├── .babelrc ├── .eslintrc ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── BUG_REPORT.yml │ └── config.yml └── pull_request_template.md ├── .gitignore ├── .stylelintrc ├── LICENSE ├── README.md ├── dist ├── css │ ├── splide-core.min.css │ ├── splide.min.css │ └── themes │ │ ├── splide-default.min.css │ │ ├── splide-sea-green.min.css │ │ └── splide-skyblue.min.css ├── js │ ├── splide-renderer.min.js │ ├── splide-renderer.min.js.map │ ├── splide.cjs.js │ ├── splide.esm.js │ ├── splide.js │ ├── splide.min.js │ ├── splide.min.js.gz │ ├── splide.min.js.map │ └── utils │ │ ├── splide-utils.cjs.js │ │ └── splide-utils.esm.js └── types │ └── index.d.ts ├── images ├── javascript-logo.svg ├── lighthouse-mobile.png ├── logo.svg ├── react-logo.svg ├── svelte-logo.svg ├── typescript-logo.svg └── vue-logo.svg ├── jest.config.js ├── package-lock.json ├── package.json ├── scripts ├── build-css.js ├── build-module.js ├── build-script.js ├── build-types.js ├── build-utils.js ├── constants │ └── banner.js ├── develop.js ├── plugins │ └── minify.js └── serve.js ├── src ├── css │ ├── core │ │ ├── foundation │ │ │ └── animations.scss │ │ ├── index.scss │ │ └── object │ │ │ ├── modifiers │ │ │ ├── draggable.scss │ │ │ ├── fade.scss │ │ │ ├── index.scss │ │ │ ├── rtl.scss │ │ │ └── ttb.scss │ │ │ └── objects │ │ │ ├── container.scss │ │ │ ├── index.scss │ │ │ ├── list.scss │ │ │ ├── pagination.scss │ │ │ ├── progress.scss │ │ │ ├── root.scss │ │ │ ├── slide.scss │ │ │ ├── spinner.scss │ │ │ ├── sr.scss │ │ │ ├── toggle.scss │ │ │ └── track.scss │ ├── template │ │ └── default │ │ │ ├── foundation │ │ │ ├── colors.scss │ │ │ └── mixins.scss │ │ │ ├── index.scss │ │ │ └── object │ │ │ ├── modifiers │ │ │ ├── index.scss │ │ │ ├── nav.scss │ │ │ ├── rtl.scss │ │ │ └── ttb.scss │ │ │ └── objects │ │ │ ├── arrow.scss │ │ │ ├── index.scss │ │ │ ├── pagination.scss │ │ │ ├── progress.scss │ │ │ ├── slide.scss │ │ │ └── toggle.scss │ └── themes │ │ ├── default │ │ └── index.scss │ │ ├── sea-green │ │ ├── index.scss │ │ └── object │ │ │ └── objects │ │ │ ├── arrow.scss │ │ │ ├── index.scss │ │ │ └── root.scss │ │ └── skyblue │ │ └── index.scss └── js │ ├── build │ ├── default.ts │ └── renderer.ts │ ├── components │ ├── Arrows │ │ ├── Arrows.ts │ │ ├── path.ts │ │ └── test │ │ │ ├── loop.test.ts │ │ │ └── slide.test.ts │ ├── Autoplay │ │ ├── Autoplay.ts │ │ ├── constants.ts │ │ └── test │ │ │ ├── autoToggle.test.ts │ │ │ ├── button.test.ts │ │ │ ├── event.test.ts │ │ │ └── general.test.ts │ ├── Clones │ │ ├── Clones.ts │ │ └── test │ │ │ └── general.test.ts │ ├── Controller │ │ ├── Controller.ts │ │ └── test │ │ │ ├── getAdjacent.test.ts │ │ │ ├── getEnd.test.ts │ │ │ ├── isBusy.test.ts │ │ │ ├── loop.test.ts │ │ │ ├── rewind.test.ts │ │ │ ├── scroll.test.ts │ │ │ ├── slide.test.ts │ │ │ ├── toIndex.test.ts │ │ │ └── toPage.test.ts │ ├── Cover │ │ └── Cover.ts │ ├── Direction │ │ ├── Direction.ts │ │ └── test │ │ │ └── general.test.ts │ ├── Drag │ │ ├── Drag.ts │ │ ├── constants.ts │ │ └── test │ │ │ ├── general.test.ts │ │ │ └── rewind.test.ts │ ├── Elements │ │ ├── Elements.ts │ │ └── test │ │ │ ├── attributes.test.ts │ │ │ ├── focus.test.ts │ │ │ └── general.test.ts │ ├── Keyboard │ │ ├── Keyboard.ts │ │ └── test │ │ │ └── general.test.ts │ ├── Layout │ │ ├── Layout.ts │ │ └── test │ │ │ ├── general.test.ts │ │ │ ├── ltr.test.ts │ │ │ ├── rtl.test.ts │ │ │ └── ttb.test.ts │ ├── LazyLoad │ │ ├── LazyLoad.ts │ │ ├── constants.ts │ │ └── test │ │ │ ├── nearby.test.ts │ │ │ └── sequential.test.ts │ ├── Live │ │ ├── Live.ts │ │ └── test │ │ │ └── general.test.ts │ ├── Media │ │ ├── Media.ts │ │ └── test │ │ │ └── general.test.ts │ ├── Move │ │ ├── Move.ts │ │ └── test │ │ │ ├── general.test.ts │ │ │ └── move.test.ts │ ├── Pagination │ │ ├── Pagination.ts │ │ └── test │ │ │ ├── direction.test.ts │ │ │ ├── general.test.ts │ │ │ ├── placeholder.test.ts │ │ │ └── tab.test.ts │ ├── Scroll │ │ ├── Scroll.ts │ │ ├── constants.ts │ │ └── test │ │ │ └── general.test.ts │ ├── Slides │ │ ├── Slide.ts │ │ ├── Slides.ts │ │ └── test │ │ │ ├── add.test.ts │ │ │ ├── filter.test.ts │ │ │ ├── general.test.ts │ │ │ ├── remove.test.ts │ │ │ └── slide.test.ts │ ├── Sync │ │ ├── Sync.ts │ │ └── test │ │ │ ├── navigation.test.ts │ │ │ └── sync.test.ts │ ├── Wheel │ │ ├── Wheel.ts │ │ └── test │ │ │ ├── general.test.ts │ │ │ └── inertia.test.ts │ ├── index.ts │ └── types.ts │ ├── constants │ ├── arrows.ts │ ├── attributes.ts │ ├── classes.ts │ ├── defaults.ts │ ├── directions.ts │ ├── events.ts │ ├── i18n.ts │ ├── listener-options.ts │ ├── media.ts │ ├── project.ts │ ├── states.ts │ └── types.ts │ ├── constructors │ ├── EventBinder │ │ ├── EventBinder.ts │ │ └── test │ │ │ ├── bind.test.ts │ │ │ ├── destroy.test.ts │ │ │ ├── dispatch.test.ts │ │ │ └── unbind.test.ts │ ├── EventInterface │ │ ├── EventInterface.ts │ │ └── test │ │ │ └── general.test.ts │ ├── RequestInterval │ │ ├── RequestInterval.ts │ │ └── test │ │ │ └── general.test.ts │ ├── State │ │ └── State.ts │ ├── Throttle │ │ ├── Throttle.ts │ │ └── test │ │ │ └── Throttle.test.ts │ └── index.ts │ ├── core │ └── Splide │ │ ├── Splide.ts │ │ └── test │ │ └── general.test.ts │ ├── index.ts │ ├── renderer │ ├── SplideRenderer │ │ └── SplideRenderer.ts │ ├── Style │ │ └── Style.ts │ ├── constants │ │ ├── classes.ts │ │ └── defaults.ts │ └── types │ │ └── types.ts │ ├── test │ ├── assets │ │ ├── css │ │ │ ├── planet.css │ │ │ └── styles.css │ │ └── images │ │ │ ├── pics │ │ │ ├── slide01.jpg │ │ │ ├── slide02.jpg │ │ │ ├── slide03.jpg │ │ │ ├── slide04.jpg │ │ │ ├── slide05.jpg │ │ │ ├── slide06.jpg │ │ │ ├── slide07.jpg │ │ │ ├── slide08.jpg │ │ │ ├── slide09.jpg │ │ │ ├── slide10.jpg │ │ │ ├── slide11.jpg │ │ │ ├── slide12.jpg │ │ │ ├── slide13.jpg │ │ │ ├── slide14.jpg │ │ │ ├── slide15.jpg │ │ │ ├── slide16.jpg │ │ │ ├── slide17.jpg │ │ │ ├── slide18.jpg │ │ │ ├── slide19.jpg │ │ │ └── slide20.jpg │ │ │ └── planets │ │ │ ├── mars.jpg │ │ │ ├── neptune.jpg │ │ │ └── saturn.jpg │ ├── fixtures │ │ ├── constants.ts │ │ ├── html.ts │ │ └── index.ts │ ├── html │ │ └── index.html │ ├── index.ts │ ├── jest │ │ └── setup.ts │ ├── php │ │ ├── examples │ │ │ ├── add.php │ │ │ ├── autoHeight.php │ │ │ ├── autoWidth.php │ │ │ ├── autoplay.php │ │ │ ├── body.php │ │ │ ├── breakpoints.php │ │ │ ├── container.php │ │ │ ├── default.php │ │ │ ├── drag-free.php │ │ │ ├── events.php │ │ │ ├── extension.php │ │ │ ├── fade.php │ │ │ ├── fixedSize.php │ │ │ ├── json.php │ │ │ ├── lazyLoad.php │ │ │ ├── liveRegions.php │ │ │ ├── multiple.php │ │ │ ├── nest.php │ │ │ ├── overflow.php │ │ │ ├── renderer.php │ │ │ ├── rtl.php │ │ │ ├── sync.php │ │ │ └── ttb.php │ │ ├── parts.php │ │ └── settings.php │ └── utils │ │ ├── index.ts │ │ └── utils.ts │ ├── transitions │ ├── Fade │ │ └── Fade.ts │ ├── Slide │ │ └── Slide.ts │ └── index.ts │ ├── types │ ├── components.ts │ ├── events.ts │ ├── general.ts │ ├── index.ts │ ├── options.ts │ └── utils.ts │ └── utils │ ├── array │ ├── empty │ │ ├── empty.test.ts │ │ └── empty.ts │ ├── forEach │ │ ├── forEach.test.ts │ │ └── forEach.ts │ ├── includes │ │ ├── includes.test.ts │ │ └── includes.ts │ ├── index.ts │ ├── push │ │ ├── push.test.ts │ │ └── push.ts │ └── toArray │ │ ├── toArray.test.ts │ │ └── toArray.ts │ ├── arrayLike │ ├── find │ │ ├── find.test.ts │ │ └── find.ts │ ├── index.ts │ └── slice │ │ ├── slice.test.ts │ │ └── slice.ts │ ├── dom │ ├── addClass │ │ ├── addClass.test.ts │ │ └── addClass.ts │ ├── append │ │ ├── append.test.ts │ │ └── append.ts │ ├── before │ │ ├── before.test.ts │ │ └── before.ts │ ├── child │ │ ├── child.test.ts │ │ └── child.ts │ ├── children │ │ ├── children.test.ts │ │ └── children.ts │ ├── closest │ │ ├── closest.test.ts │ │ └── closest.ts │ ├── create │ │ ├── create.test.ts │ │ └── create.ts │ ├── display │ │ ├── display.test.ts │ │ └── display.ts │ ├── focus │ │ ├── focus.test.ts │ │ └── focus.ts │ ├── getAttribute │ │ ├── getAttribute.test.ts │ │ └── getAttribute.ts │ ├── hasClass │ │ ├── hasClass.test.ts │ │ └── hasClass.ts │ ├── index.ts │ ├── matches │ │ ├── matches.test.ts │ │ └── matches.ts │ ├── measure │ │ └── measure.ts │ ├── normalizeKey │ │ ├── normalizeKey.test.ts │ │ └── normalizeKey.ts │ ├── parseHtml │ │ ├── parseHtml.test.ts │ │ └── parseHtml.ts │ ├── prevent │ │ ├── prevent.test.ts │ │ └── prevent.ts │ ├── query │ │ ├── query.test.ts │ │ └── query.ts │ ├── queryAll │ │ ├── queryAll.test.ts │ │ └── queryAll.ts │ ├── rect │ │ ├── rect.test.ts │ │ └── rect.ts │ ├── remove │ │ ├── remove.test.ts │ │ └── remove.ts │ ├── removeAttribute │ │ ├── removeAttribute.test.ts │ │ └── removeAttribute.ts │ ├── removeClass │ │ ├── removeClass.test.ts │ │ └── removeClass.ts │ ├── setAttribute │ │ ├── setAttribute.test.ts │ │ └── setAttribute.ts │ ├── style │ │ ├── style.test.ts │ │ └── style.ts │ ├── timeOf │ │ ├── timeOf.test.ts │ │ └── timeOf.ts │ ├── toggleClass │ │ ├── toggleClass.test.ts │ │ └── toggleClass.ts │ └── unit │ │ ├── unit.test.ts │ │ └── unit.ts │ ├── error │ ├── assert │ │ └── assert.ts │ ├── error │ │ └── error.ts │ └── index.ts │ ├── function │ ├── apply │ │ ├── apply.test.ts │ │ └── apply.ts │ ├── index.ts │ ├── nextTick │ │ └── nextTick.ts │ ├── noop │ │ └── noop.ts │ └── raf │ │ └── raf.ts │ ├── index.ts │ ├── math │ ├── approximatelyEqual │ │ ├── approximatelyEqual.test.ts │ │ └── approximatelyEqual.ts │ ├── between │ │ ├── between.test.ts │ │ └── between.ts │ ├── clamp │ │ ├── clamp.test.ts │ │ └── clamp.ts │ ├── index.ts │ ├── math │ │ └── math.ts │ └── sign │ │ ├── sign.test.ts │ │ └── sign.ts │ ├── object │ ├── assign │ │ ├── assign.test.ts │ │ └── assign.ts │ ├── forOwn │ │ ├── forOwn.test.ts │ │ └── forOwn.ts │ ├── index.ts │ ├── merge │ │ ├── merge.test.ts │ │ └── merge.ts │ ├── omit │ │ ├── omit.test.ts │ │ └── omit.ts │ └── ownKeys │ │ └── ownKeys.ts │ ├── string │ ├── camelToKebab │ │ ├── camelToKebab.test.ts │ │ └── camelToKebab.ts │ ├── format │ │ ├── format.test.ts │ │ └── format.ts │ ├── index.ts │ ├── pad │ │ ├── pad.test.ts │ │ └── pad.ts │ └── uniqueId │ │ ├── uniqueId.test.ts │ │ └── uniqueId.ts │ └── type │ ├── type.test.ts │ └── type.ts └── tsconfig.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/env", 5 | { 6 | "modules": false, 7 | "loose": true 8 | } 9 | ] 10 | ], 11 | "env": { 12 | "test": { 13 | "presets": [ 14 | [ 15 | "@babel/env" 16 | ] 17 | ] 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: NaotoshiFujita -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | ## Related Issues 6 | 7 | 10 | 11 | ## Description 12 | 13 | 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | 3 | local/ 4 | 5 | .idea/ 6 | .vscode/ 7 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-sass-guidelines", 3 | "plugins": [ 4 | "stylelint-scss", 5 | "stylelint-order" 6 | ], 7 | "rules": { 8 | "number-leading-zero": "never", 9 | "max-nesting-depth": 7, 10 | "selector-max-compound-selectors": null, 11 | "block-no-empty": null, 12 | "function-parentheses-space-inside": "always" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 Naotoshi Fujita 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /dist/css/splide-core.min.css: -------------------------------------------------------------------------------- 1 | @keyframes splide-loading{0%{transform:rotate(0)}to{transform:rotate(1turn)}}.splide__track--draggable{-webkit-touch-callout:none;-webkit-user-select:none;-ms-user-select:none;user-select:none}.splide__track--fade>.splide__list>.splide__slide{margin:0!important;opacity:0;z-index:0}.splide__track--fade>.splide__list>.splide__slide.is-active{opacity:1;z-index:1}.splide--rtl{direction:rtl}.splide__track--ttb>.splide__list{display:block}.splide__container{box-sizing:border-box;position:relative}.splide__list{backface-visibility:hidden;display:-ms-flexbox;display:flex;height:100%;margin:0!important;padding:0!important}.splide.is-initialized:not(.is-active) .splide__list{display:block}.splide__pagination{-ms-flex-align:center;align-items:center;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:center;justify-content:center;margin:0;pointer-events:none}.splide__pagination li{display:inline-block;line-height:1;list-style-type:none;margin:0;pointer-events:auto}.splide:not(.is-overflow) .splide__pagination{display:none}.splide__progress__bar{width:0}.splide{position:relative;visibility:hidden}.splide.is-initialized,.splide.is-rendered{visibility:visible}.splide__slide{backface-visibility:hidden;box-sizing:border-box;-ms-flex-negative:0;flex-shrink:0;list-style-type:none!important;margin:0;position:relative}.splide__slide img{vertical-align:bottom}.splide__spinner{animation:splide-loading 1s linear infinite;border:2px solid #999;border-left-color:transparent;border-radius:50%;bottom:0;contain:strict;display:inline-block;height:20px;left:0;margin:auto;position:absolute;right:0;top:0;width:20px}.splide__sr{clip:rect(0 0 0 0);border:0;height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.splide__toggle.is-active .splide__toggle__play,.splide__toggle__pause{display:none}.splide__toggle.is-active .splide__toggle__pause{display:inline}.splide__track{overflow:hidden;position:relative;z-index:0} -------------------------------------------------------------------------------- /dist/js/splide.min.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splidejs/splide/d7e1f08e6b4f4b02a7c6ccbfbeb2d569d85715e6/dist/js/splide.min.js.gz -------------------------------------------------------------------------------- /images/javascript-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /images/lighthouse-mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splidejs/splide/d7e1f08e6b4f4b02a7c6ccbfbeb2d569d85715e6/images/lighthouse-mobile.png -------------------------------------------------------------------------------- /images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /images/svelte-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /images/typescript-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /images/vue-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rootDir: './src', 3 | transform: { 4 | '^.+\\.(ts|tsx)$': 'ts-jest', 5 | }, 6 | testEnvironment: 'jsdom', 7 | setupFiles: [ 8 | './js/test/jest/setup.ts', 9 | ], 10 | transformIgnorePatterns: [ 11 | '/node_modules/@babel', 12 | '/node_modules/@jest', 13 | ], 14 | }; 15 | -------------------------------------------------------------------------------- /scripts/build-css.js: -------------------------------------------------------------------------------- 1 | const sass = require( 'sass' ); 2 | const fs = require( 'fs' ).promises; 3 | const path = require( 'path' ); 4 | const postcss = require( 'postcss' ); 5 | const cssnano = require( 'cssnano' ); 6 | const autoprefixer = require( 'autoprefixer' ); 7 | const name = 'splide'; 8 | 9 | const files = [ 10 | './src/css/core/index.scss', 11 | './src/css/themes/default/index.scss', 12 | './src/css/themes/sea-green/index.scss', 13 | './src/css/themes/skyblue/index.scss', 14 | ]; 15 | 16 | function buildCss( file ) { 17 | const result = sass.renderSync( { file, outputStyle: 'compressed' } ); 18 | const outFile = rename( file ); 19 | 20 | return postcss( [ 21 | cssnano( { reduceIdents: false } ), 22 | autoprefixer(), 23 | ] ) 24 | .process( result.css, { from: undefined } ) 25 | .then( result => { 26 | result.warnings().forEach( warn => { 27 | console.warn( warn.toString() ); 28 | } ); 29 | 30 | return fs.writeFile( outFile, result.css ).then( () => result ); 31 | } ) 32 | .then( result => { 33 | if ( outFile.includes( 'splide-default' ) ) { 34 | const dir = path.dirname( outFile ).split( '/' ).slice( 0, -1 ).join( '/' ); 35 | return fs.writeFile( `${ dir }/${ name }.min.css`, result.css ).then( () => result ); 36 | } 37 | } ); 38 | } 39 | 40 | function rename( file ) { 41 | file = file.replace( './src/', '' ); 42 | 43 | const fragments = path.dirname( file ).split( '/' ); 44 | const dirname = fragments.slice( 0, -1 ).join( '/' ); 45 | return `./dist/${ dirname }/${ name }-${ fragments[ fragments.length - 1 ] }.min.css`; 46 | } 47 | 48 | Promise.all( files.map( buildCss ) ).catch( e => console.error( e ) ); 49 | 50 | exports.buildCss = () => { 51 | files.forEach( buildCss ); 52 | }; 53 | -------------------------------------------------------------------------------- /scripts/build-module.js: -------------------------------------------------------------------------------- 1 | const rollup = require( 'rollup' ).rollup; 2 | const resolve = require( '@rollup/plugin-node-resolve' ).nodeResolve; 3 | const esbuild = require( 'rollup-plugin-esbuild' ).default; 4 | const banner = require( './constants/banner' ); 5 | const babel = require( '@rollup/plugin-babel' ); 6 | const path = require( 'path' ); 7 | const name = 'splide'; 8 | 9 | 10 | function buildModule( type ) { 11 | return rollup( { 12 | input: './src/js/index.ts', 13 | plugins: [ 14 | resolve(), 15 | esbuild(), 16 | babel.getBabelOutputPlugin( { 17 | configFile: path.resolve( __dirname, '../.babelrc' ), 18 | allowAllFormats: true, 19 | } ), 20 | ], 21 | } ).then( bundle => { 22 | return bundle.write( { 23 | banner, 24 | file : `./dist/js/${ name }.${ type }.js`, 25 | format : type, 26 | sourcemap: false, 27 | exports : 'named', 28 | } ); 29 | } ); 30 | } 31 | 32 | Promise.all( [ buildModule( 'cjs' ), buildModule( 'esm' ) ] ).catch( e => console.error( e ) ); 33 | 34 | exports.buildCjs = () => buildModule( 'cjs' ); 35 | exports.buildEsm = () => buildModule( 'esm' ); 36 | -------------------------------------------------------------------------------- /scripts/build-script.js: -------------------------------------------------------------------------------- 1 | const rollup = require( 'rollup' ).rollup; 2 | const esbuild = require( 'rollup-plugin-esbuild' ).default; 3 | const babel = require( '@rollup/plugin-babel' ); 4 | const resolve = require( '@rollup/plugin-node-resolve' ).nodeResolve; 5 | const path = require( 'path' ); 6 | const minify = require( './plugins/minify' ).minify; 7 | const banner = require( './constants/banner' ); 8 | const fs = require( 'fs' ).promises; 9 | const zlib = require( 'zlib' ); 10 | const name = 'splide'; 11 | 12 | 13 | async function buildScript( compress, type = 'default' ) { 14 | const file = `./dist/js/${ name }${ type !== 'default' ? `-${ type }` : '' }${ compress ? '.min' : '' }.js`; 15 | 16 | const bundle = await rollup( { 17 | input: `./src/js/build/${ type }.ts`, 18 | plugins: [ 19 | resolve(), 20 | esbuild( { minify: false } ), 21 | babel.getBabelOutputPlugin( { 22 | configFile: path.resolve( __dirname, '../.babelrc' ), 23 | allowAllFormats: true, 24 | } ), 25 | compress ? minify() : false, 26 | ], 27 | } ); 28 | 29 | await bundle.write( { 30 | banner, 31 | file, 32 | format : 'umd', 33 | name : type === 'default' ? 'Splide' : 'SplideRenderer', 34 | sourcemap: compress, 35 | } ); 36 | 37 | if ( compress && type === 'default' ) { 38 | await fs.readFile( file ).then( content => { 39 | return new Promise( ( resolve, reject ) => { 40 | zlib.gzip( content, ( err, binary ) => { 41 | if ( err ) { 42 | return reject( err ); 43 | } 44 | 45 | fs.writeFile( `${ file }.gz`, binary ).then( resolve, reject ); 46 | } ); 47 | } ); 48 | } ); 49 | } 50 | } 51 | 52 | Promise.all( [ 53 | buildScript(), 54 | buildScript( true ), 55 | buildScript( true, 'renderer' ), 56 | ] ).catch( console.error ); 57 | 58 | exports.buildJs = () => buildScript(); 59 | exports.buildMin = () => buildScript( true ); 60 | exports.buildRenderer = () => buildScript( true, 'renderer' ); 61 | -------------------------------------------------------------------------------- /scripts/build-types.js: -------------------------------------------------------------------------------- 1 | const { default: dts } = require( 'rollup-plugin-dts' ); 2 | const { rollup } = require( 'rollup' ); 3 | const { promises: fs } = require( 'fs' ); 4 | const path = require( 'path' ); 5 | const util = require( 'util' ); 6 | const exec = util.promisify( require( 'child_process' ).exec ); 7 | const dir = './dist/types'; 8 | 9 | 10 | async function clean() { 11 | const files = await fs.readdir( dir ); 12 | 13 | await Promise.all( files.map( file => { 14 | if ( file !== 'index.d.ts' ) { 15 | return fs.rm( path.join( dir, file ), { recursive: true, force: true } ); 16 | } 17 | } ) ); 18 | } 19 | 20 | async function emit() { 21 | await exec( 'tsc --emitDeclarationOnly' ); 22 | } 23 | 24 | async function bundle() { 25 | const file = path.join( dir, 'index.d.ts' ); 26 | 27 | const bundle = await rollup( { 28 | input : file, 29 | plugins: [ dts( { respectExternal: true } ) ], 30 | } ); 31 | 32 | await bundle.write( { file } ); 33 | } 34 | 35 | async function build() { 36 | await clean(); 37 | await emit(); 38 | await bundle(); 39 | await clean(); 40 | } 41 | 42 | build().catch( e => console.error( e ) ); 43 | -------------------------------------------------------------------------------- /scripts/build-utils.js: -------------------------------------------------------------------------------- 1 | const rollup = require( 'rollup' ).rollup; 2 | const esbuild = require( 'rollup-plugin-esbuild' ).default; 3 | const name = 'splide-utils'; 4 | 5 | 6 | function buildModule( type ) { 7 | return rollup( { 8 | input: './src/js/utils/index.ts', 9 | plugins: [ 10 | esbuild(), 11 | ], 12 | } ).then( bundle => { 13 | return bundle.write( { 14 | file : `./dist/js/utils/${ name }.${ type }.js`, 15 | format : type, 16 | sourcemap: false, 17 | exports : 'named', 18 | } ); 19 | } ); 20 | } 21 | 22 | Promise.all( [ buildModule( 'cjs' ), buildModule( 'esm' ) ] ).catch( e => console.error( e ) ); -------------------------------------------------------------------------------- /scripts/constants/banner.js: -------------------------------------------------------------------------------- 1 | const info = require( '../../package.json' ); 2 | 3 | module.exports = `/*! 4 | * Splide.js 5 | * Version : ${ info.version } 6 | * License : ${ info.license } 7 | * Copyright: ${ new Date().getFullYear() } ${ info.author } 8 | */`; 9 | -------------------------------------------------------------------------------- /scripts/develop.js: -------------------------------------------------------------------------------- 1 | const chokidar = require( 'chokidar' ); 2 | const { buildJs, buildRenderer } = require( './build-script' ); 3 | const { buildCss } = require( './build-css' ); 4 | 5 | chokidar.watch( [ './src/js/**/*.ts', '!*.test.ts', '!./src/js/renderer/**/*.ts' ] ).on( 'change', async () => { 6 | console.log( 'Building Script...' ); 7 | await buildJs() 8 | console.log( 'Finished' ); 9 | } ); 10 | 11 | chokidar.watch( [ './src/js/renderer/**/*.ts', '!*.test.ts' ] ).on( 'change', async () => { 12 | console.log( 'Building Renderer Script...' ); 13 | await buildRenderer() 14 | console.log( 'Finished' ); 15 | } ); 16 | 17 | chokidar.watch( [ './src/css/**/*.scss' ] ).on( 'change', async () => { 18 | console.log( 'Building CSS...' ); 19 | await buildCss() 20 | console.log( 'Finished' ); 21 | } ); 22 | 23 | -------------------------------------------------------------------------------- /scripts/plugins/minify.js: -------------------------------------------------------------------------------- 1 | const uglify = require( 'uglify-js' ); 2 | 3 | const DEFAULTS = { 4 | minify: { 5 | sourceMap: true, 6 | output: { 7 | comments: /^!/, 8 | }, 9 | toplevel: true, 10 | mangle: { 11 | properties: { 12 | regex: /^_/, 13 | }, 14 | }, 15 | }, 16 | }; 17 | 18 | function minify( pluginOptions = {} ) { 19 | pluginOptions = { ...DEFAULTS, ...pluginOptions }; 20 | 21 | return { 22 | name: 'minify', 23 | renderChunk( code ) { 24 | const result = uglify.minify( code, pluginOptions.minify ); 25 | 26 | if ( result.error ) { 27 | throw new Error( result.error ); 28 | } 29 | 30 | return result; 31 | }, 32 | }; 33 | } 34 | 35 | exports.minify = minify; 36 | -------------------------------------------------------------------------------- /scripts/serve.js: -------------------------------------------------------------------------------- 1 | const http = require( 'http' ); 2 | const path = require( 'path' ); 3 | const fs = require( 'fs' ).promises; 4 | const server = http.createServer(); 5 | 6 | const mime = { 7 | '.html': 'text/html', 8 | '.css' : 'text/css', 9 | '.jpg' : 'image/jpeg', 10 | '.js' : 'application/javascript', 11 | }; 12 | 13 | server.on( 'request', async ( request, response ) => { 14 | const { url } = request; 15 | let fullPath; 16 | 17 | if ( url === '/' ) { 18 | fullPath = path.resolve( './src/js/test/html/index.html' ); 19 | } else if ( url.startsWith( '/' ) ) { 20 | fullPath = path.resolve( `.${ url }` ); 21 | } else { 22 | fullPath = url; 23 | } 24 | 25 | const type = mime[ path.extname( fullPath ) ] || 'text/plain'; 26 | const buffer = await fs.readFile( fullPath ).catch( e => console.warn( e ) ); 27 | 28 | response.writeHead( 200, { 'Content-Type': type } ); 29 | response.end( buffer ); 30 | } ); 31 | 32 | server.listen( 3000 ); 33 | -------------------------------------------------------------------------------- /src/css/core/foundation/animations.scss: -------------------------------------------------------------------------------- 1 | @keyframes splide-loading { 2 | 0% { 3 | transform: rotateZ( 0 ); 4 | } 5 | 6 | 100% { 7 | transform: rotateZ( 360deg ); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/css/core/index.scss: -------------------------------------------------------------------------------- 1 | @forward 'foundation/animations'; 2 | @forward 'object/modifiers'; 3 | @forward 'object/objects'; 4 | -------------------------------------------------------------------------------- /src/css/core/object/modifiers/draggable.scss: -------------------------------------------------------------------------------- 1 | .splide { 2 | $root: &; 3 | 4 | &__track--draggable { 5 | -webkit-touch-callout: none; 6 | user-select: none; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/css/core/object/modifiers/fade.scss: -------------------------------------------------------------------------------- 1 | .splide { 2 | $root: &; 3 | 4 | &__track--fade { 5 | > #{ $root }__list { 6 | > #{ $root }__slide { 7 | margin: 0 !important; 8 | opacity: 0; 9 | z-index: 0; 10 | 11 | &.is-active { 12 | opacity: 1; 13 | z-index: 1; 14 | } 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/css/core/object/modifiers/index.scss: -------------------------------------------------------------------------------- 1 | @forward 'draggable'; 2 | @forward 'fade'; 3 | @forward 'rtl'; 4 | @forward 'ttb'; 5 | -------------------------------------------------------------------------------- /src/css/core/object/modifiers/rtl.scss: -------------------------------------------------------------------------------- 1 | .splide { 2 | &--rtl { 3 | direction: rtl; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/css/core/object/modifiers/ttb.scss: -------------------------------------------------------------------------------- 1 | .splide { 2 | $root: &; 3 | 4 | &__track--ttb { 5 | > #{ $root }__list { 6 | display: block; 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/css/core/object/objects/container.scss: -------------------------------------------------------------------------------- 1 | .splide { 2 | &__container { 3 | box-sizing: border-box; 4 | position: relative; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/css/core/object/objects/index.scss: -------------------------------------------------------------------------------- 1 | @forward 'container'; 2 | @forward 'list'; 3 | @forward 'pagination'; 4 | @forward 'progress'; 5 | @forward 'root'; 6 | @forward 'slide'; 7 | @forward 'spinner' as spinner-*; 8 | @forward 'sr'; 9 | @forward 'toggle'; 10 | @forward 'track'; 11 | -------------------------------------------------------------------------------- /src/css/core/object/objects/list.scss: -------------------------------------------------------------------------------- 1 | .splide { 2 | $root: &; 3 | 4 | &__list { 5 | backface-visibility: hidden; 6 | display: flex; 7 | height: 100%; 8 | margin: 0 !important; 9 | padding: 0 !important; 10 | } 11 | 12 | &.is-initialized:not( .is-active ) { 13 | #{ $root }__list { 14 | display: block; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/css/core/object/objects/pagination.scss: -------------------------------------------------------------------------------- 1 | .splide { 2 | $root: &; 3 | 4 | &__pagination { 5 | align-items: center; 6 | display: flex; 7 | flex-wrap: wrap; 8 | justify-content: center; 9 | margin: 0; 10 | pointer-events: none; 11 | 12 | li { 13 | display: inline-block; 14 | line-height: 1; 15 | list-style-type: none; 16 | margin: 0; 17 | pointer-events: auto; 18 | } 19 | } 20 | 21 | &:not( .is-overflow ) { 22 | #{ $root }__pagination { 23 | display: none; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/css/core/object/objects/progress.scss: -------------------------------------------------------------------------------- 1 | .splide { 2 | &__progress { 3 | &__bar { 4 | width: 0; 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/css/core/object/objects/root.scss: -------------------------------------------------------------------------------- 1 | .splide { 2 | position: relative; 3 | visibility: hidden; 4 | 5 | &.is-initialized, 6 | &.is-rendered { 7 | visibility: visible; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/css/core/object/objects/slide.scss: -------------------------------------------------------------------------------- 1 | .splide { 2 | &__slide { 3 | backface-visibility: hidden; 4 | box-sizing: border-box; 5 | flex-shrink: 0; 6 | list-style-type: none !important; 7 | margin: 0; 8 | position: relative; 9 | 10 | img { 11 | vertical-align: bottom; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/css/core/object/objects/spinner.scss: -------------------------------------------------------------------------------- 1 | $size: 20px !default; 2 | $border: 2px solid #999 !default; 3 | 4 | .splide { 5 | &__spinner { 6 | animation: splide-loading 1s infinite linear; 7 | border: $border; 8 | border-left-color: transparent; 9 | border-radius: 50%; 10 | bottom: 0; 11 | contain: strict; 12 | display: inline-block; 13 | height: $size; 14 | left: 0; 15 | margin: auto; 16 | position: absolute; 17 | right: 0; 18 | top: 0; 19 | width: $size; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/css/core/object/objects/sr.scss: -------------------------------------------------------------------------------- 1 | .splide { 2 | &__sr { 3 | border: 0; 4 | clip: rect( 0 0 0 0 ); 5 | height: 1px; 6 | margin: -1px; 7 | overflow: hidden; 8 | padding: 0; 9 | position: absolute; 10 | width: 1px; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/css/core/object/objects/toggle.scss: -------------------------------------------------------------------------------- 1 | .splide { 2 | &__toggle { 3 | $parent: &; 4 | 5 | &__play { 6 | } 7 | 8 | &__pause { 9 | display: none; 10 | } 11 | 12 | &.is-active { 13 | #{ $parent }__play { 14 | display: none; 15 | } 16 | 17 | #{ $parent }__pause { 18 | display: inline; 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/css/core/object/objects/track.scss: -------------------------------------------------------------------------------- 1 | .splide { 2 | &__track { 3 | overflow: hidden; 4 | position: relative; 5 | z-index: 0; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/css/template/default/foundation/colors.scss: -------------------------------------------------------------------------------- 1 | $main: #000 !default; 2 | $sub01: #333 !default; 3 | $sub02: #999 !default; 4 | 5 | $background: #ccc !default; 6 | $background-active: #fff !default; 7 | 8 | $focus: #0bf !default; 9 | -------------------------------------------------------------------------------- /src/css/template/default/foundation/mixins.scss: -------------------------------------------------------------------------------- 1 | @use './colors'; 2 | 3 | $outline: colors.$focus 3px solid !default; 4 | $outline-offset: 3px !default; 5 | $outline-offset-inset: -3px !default; 6 | 7 | @mixin ie-only { 8 | @media screen and (-ms-high-contrast: none) { 9 | @content; 10 | } 11 | } 12 | 13 | @mixin focus-outline( $offset: $outline-offset ) { 14 | outline: $outline; 15 | outline-offset: $offset; 16 | } 17 | 18 | @mixin focus-outline-inset { 19 | @supports ( outline-offset: $outline-offset-inset ) { 20 | @include focus-outline( $outline-offset-inset ); 21 | } 22 | 23 | /* IE does not support outline-offset */ 24 | @include ie-only { 25 | border: $outline; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/css/template/default/index.scss: -------------------------------------------------------------------------------- 1 | @use 'foundation/colors'; 2 | 3 | $spinner-size: 20px !default; 4 | $spinner-border: 2px solid colors.$sub02 !default; 5 | 6 | @use '../../core/object/objects' with ( 7 | $spinner-size: $spinner-size, 8 | $spinner-border: $spinner-border, 9 | ); 10 | 11 | @forward '../../core'; 12 | @forward 'object/objects'; 13 | @forward 'object/modifiers'; 14 | -------------------------------------------------------------------------------- /src/css/template/default/object/modifiers/index.scss: -------------------------------------------------------------------------------- 1 | @forward 'nav' as nav-*; 2 | @forward 'rtl' as rtl-*; 3 | @forward 'ttb' as ttb-*; 4 | -------------------------------------------------------------------------------- /src/css/template/default/object/modifiers/nav.scss: -------------------------------------------------------------------------------- 1 | @use '../../foundation/colors'; 2 | @use '../../foundation/mixins'; 3 | 4 | $border: 3px solid transparent !default; 5 | $border-active: 3px solid colors.$main !default; 6 | $border-radius: false !default; 7 | $opacity: false !default; 8 | $opacity-active: false !default; 9 | 10 | .splide { 11 | $root: &; 12 | 13 | &__track--nav { 14 | > #{ $root }__list { 15 | > #{ $root }__slide { 16 | border: $border; 17 | cursor: pointer; 18 | 19 | @if $opacity { 20 | opacity: $opacity; 21 | } 22 | 23 | @if $border-radius { 24 | border-radius: $border-radius; 25 | } 26 | 27 | &.is-active { 28 | border: $border-active; 29 | 30 | @if $opacity { 31 | opacity: $opacity-active; 32 | } 33 | } 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/css/template/default/object/modifiers/rtl.scss: -------------------------------------------------------------------------------- 1 | $arrow-right: 1em !default; 2 | $arrow-left: 1em !default; 3 | 4 | .splide { 5 | $root: &; 6 | 7 | &__arrows--rtl { 8 | #{ $root }__arrow { 9 | &--prev { 10 | left: auto; 11 | right: $arrow-right; 12 | 13 | svg { 14 | transform: scaleX( 1 ); 15 | } 16 | } 17 | 18 | &--next { 19 | left: $arrow-left; 20 | right: auto; 21 | 22 | svg { 23 | transform: scaleX( -1 ); 24 | } 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/css/template/default/object/modifiers/ttb.scss: -------------------------------------------------------------------------------- 1 | $arrow-top: 1em !default; 2 | $arrow-bottom: 1em !default; 3 | $pagination-left: auto !default; 4 | $pagination-right: .5em !default; 5 | $pagination-dot-width: false !default; 6 | $pagination-dot-height: false !default; 7 | 8 | .splide { 9 | $root: &; 10 | 11 | &__arrows--ttb { 12 | #{ $root }__arrow { 13 | left: 50%; 14 | transform: translate( -50%, 0 ); 15 | 16 | &--prev { 17 | top: $arrow-top; 18 | 19 | svg { 20 | transform: rotateZ( -90deg ); 21 | } 22 | } 23 | 24 | &--next { 25 | bottom: $arrow-bottom; 26 | top: auto; 27 | 28 | svg { 29 | transform: rotateZ( 90deg ); 30 | } 31 | } 32 | } 33 | } 34 | 35 | &__pagination--ttb { 36 | bottom: 0; 37 | display: flex; 38 | flex-direction: column; 39 | left: $pagination-left; 40 | padding: 1em 0; 41 | right: $pagination-right; 42 | top: 0; 43 | 44 | #{ $root }__pagination__page { 45 | @if $pagination-dot-width { 46 | width: $pagination-dot-width; 47 | } 48 | 49 | @if $pagination-dot-height { 50 | height: $pagination-dot-height; 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/css/template/default/object/objects/index.scss: -------------------------------------------------------------------------------- 1 | @forward 'arrow' as arrow-*; 2 | @forward 'pagination' as pagination-*; 3 | @forward 'progress' as progress-*; 4 | @forward 'slide' as slide-*; 5 | @forward 'toggle' as toggle-*; 6 | -------------------------------------------------------------------------------- /src/css/template/default/object/objects/pagination.scss: -------------------------------------------------------------------------------- 1 | @use '../../foundation/colors'; 2 | @use '../../foundation/mixins'; 3 | 4 | $bottom: .5em !default; 5 | $dot-width: 8px !default; 6 | $dot-height: 8px !default; 7 | $dot-background: colors.$background !default; 8 | $dot-background-hover: false !default; 9 | $dot-background-active: colors.$background-active !default; 10 | $dot-border: 0 !default; 11 | $dot-border-radius: 50% !default; 12 | $dot-margin: 3px !default; 13 | $dot-padding: 0 !default; 14 | $dot-opacity: .7 !default; 15 | $dot-opacity-hover: .9 !default; 16 | $dot-transition: transform .2s linear !default; 17 | $dot-transform-active: scale( 1.4 ) !default; 18 | 19 | .splide { 20 | $root: &; 21 | 22 | &__pagination { 23 | bottom: $bottom; 24 | left: 0; 25 | padding: 0 1em; 26 | position: absolute; 27 | right: 0; 28 | z-index: 1; 29 | 30 | &__page { 31 | background: $dot-background; 32 | border: $dot-border; 33 | border-radius: $dot-border-radius; 34 | display: inline-block; 35 | height: $dot-height; 36 | margin: $dot-margin; 37 | padding: $dot-padding; 38 | position: relative; 39 | transition: $dot-transition; 40 | width: $dot-width; 41 | 42 | @if $dot-opacity { 43 | opacity: $dot-opacity; 44 | } 45 | 46 | &.is-active { 47 | background: $dot-background-active; 48 | z-index: 1; 49 | 50 | @if $dot-transform-active { 51 | transform: $dot-transform-active; 52 | } 53 | } 54 | 55 | &:hover { 56 | cursor: pointer; 57 | 58 | @if $dot-opacity-hover { 59 | opacity: $dot-opacity-hover; 60 | } 61 | 62 | @if $dot-background-hover { 63 | background: $dot-background-hover; 64 | } 65 | } 66 | 67 | &:focus-visible { 68 | @include mixins.focus-outline; 69 | } 70 | } 71 | } 72 | 73 | &.is-focus-in { 74 | #{ $root }__pagination { 75 | &__page:focus { 76 | @include mixins.focus-outline; 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/css/template/default/object/objects/progress.scss: -------------------------------------------------------------------------------- 1 | @use '../../foundation/colors'; 2 | 3 | $height: 3px !default; 4 | $background: colors.$background; 5 | 6 | .splide { 7 | &__progress { 8 | &__bar { 9 | background: $background; 10 | height: $height; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/css/template/default/object/objects/slide.scss: -------------------------------------------------------------------------------- 1 | @use '../../foundation/colors'; 2 | @use '../../foundation/mixins'; 3 | 4 | $border-radius: false !default; 5 | $outline-offset: -2px !default; 6 | 7 | .splide { 8 | $root: &; 9 | 10 | &__slide { 11 | -webkit-tap-highlight-color: transparent; 12 | 13 | @if $border-radius { 14 | border-radius: $border-radius; 15 | } 16 | 17 | // Gets rid of a focus ring in IE and Safari 18 | &:focus { 19 | outline: 0; 20 | } 21 | 22 | &:focus-visible { 23 | @include mixins.focus-outline-inset; 24 | } 25 | } 26 | 27 | &.is-focus-in { 28 | #{ $root }__slide:focus { 29 | @include mixins.focus-outline-inset; 30 | } 31 | 32 | #{ $root }__track { 33 | > #{ $root }__list { 34 | > #{ $root }__slide:focus { 35 | @include mixins.ie-only { 36 | border-color: colors.$focus; 37 | } 38 | } 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/css/template/default/object/objects/toggle.scss: -------------------------------------------------------------------------------- 1 | @use '../../foundation/mixins'; 2 | 3 | .splide { 4 | $root: &; 5 | 6 | &__toggle { 7 | cursor: pointer; 8 | 9 | &:focus-visible { 10 | @include mixins.focus-outline; 11 | } 12 | } 13 | 14 | &.is-focus-in { 15 | #{ $root }__toggle:focus { 16 | @include mixins.focus-outline; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/css/themes/default/index.scss: -------------------------------------------------------------------------------- 1 | @forward '../../template/default'; 2 | -------------------------------------------------------------------------------- /src/css/themes/sea-green/index.scss: -------------------------------------------------------------------------------- 1 | $main: #20b2aa !default; 2 | $main-light: lighten( $main, 20% ) !default; 3 | 4 | @use '../../template/default/foundation/colors' with ( 5 | $main: $main, 6 | $sub01: $main, 7 | $sub02: $main, 8 | $background: #ccc, 9 | $background-active: $main, 10 | $focus: $main, 11 | ); 12 | 13 | @use '../../template/default/object/modifiers/nav' with ( 14 | $border-radius: 4px, 15 | $opacity: .7, 16 | $opacity-active: 1, 17 | ); 18 | 19 | @use '../../template/default/object/modifiers/ttb' with ( 20 | $pagination-dot-width: 5px, 21 | $pagination-dot-height: 20px, 22 | $pagination-right: 1em, 23 | ); 24 | 25 | @use '../../template/default/object/objects/arrow' with ( 26 | $size: 2.5em, 27 | $opacity: false, 28 | $opacity-hover: false, 29 | $fill-hover: $main-light, 30 | $transition: fill .2s linear, 31 | $button-size: 2.5em, 32 | $button-border-radius: 0, 33 | $button-background: transparent, 34 | ); 35 | 36 | @use '../../template/default/object/objects/pagination' with ( 37 | $bottom: 1em, 38 | $dot-width: 20px, 39 | $dot-height: 5px, 40 | $dot-border-radius: 2.5px, 41 | $dot-opacity: false, 42 | $dot-transition: background-color .2s linear, 43 | $dot-background-hover: $main-light, 44 | $dot-transform-active: false, 45 | ); 46 | 47 | @use '../../template/default/object/objects/slide' with ( 48 | $border-radius: 4px, 49 | ); 50 | 51 | @forward '../../template/default'; 52 | @forward 'object/objects'; 53 | -------------------------------------------------------------------------------- /src/css/themes/sea-green/object/objects/arrow.scss: -------------------------------------------------------------------------------- 1 | .splide { 2 | $root: &; 3 | 4 | &__slider { 5 | > #{ $root }__arrows { 6 | #{ $root }__arrow { 7 | &--prev { 8 | left: -2.5em; 9 | } 10 | 11 | &--next { 12 | right: -2.5em; 13 | } 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/css/themes/sea-green/object/objects/index.scss: -------------------------------------------------------------------------------- 1 | @forward 'arrow'; 2 | @forward 'root'; 3 | 4 | -------------------------------------------------------------------------------- /src/css/themes/sea-green/object/objects/root.scss: -------------------------------------------------------------------------------- 1 | .splide { 2 | padding: 3em; 3 | } 4 | -------------------------------------------------------------------------------- /src/css/themes/skyblue/index.scss: -------------------------------------------------------------------------------- 1 | $main: #00bfff !default; 2 | $main-light: lighten( $main, 20% ) !default; 3 | 4 | @use '../../template/default/foundation/colors' with ( 5 | $main: $main, 6 | $sub01: $main, 7 | $sub02: $main, 8 | $background: #ccc, 9 | $background-active: $main, 10 | $focus: $main, 11 | ); 12 | 13 | @use '../../template/default/object/modifiers/nav' with ( 14 | $opacity: .7, 15 | $opacity-active: 1, 16 | ); 17 | 18 | @use '../../template/default/object/objects/arrow' with ( 19 | $size: 2.5em, 20 | $opacity: false, 21 | $opacity-hover: false, 22 | $fill-hover: $main-light, 23 | $transition: fill .2s linear, 24 | $button-size: 2.5em, 25 | $button-border-radius: 0, 26 | $button-background: transparent, 27 | ); 28 | 29 | @use '../../template/default/object/objects/pagination' with ( 30 | $dot-width: 10px, 31 | $dot-height: 10px, 32 | $dot-opacity: false, 33 | $dot-transition: ( background-color .2s linear, transform .2s linear ), 34 | $dot-background-hover: $main-light, 35 | ); 36 | 37 | @forward '../../template/default'; 38 | -------------------------------------------------------------------------------- /src/js/build/default.ts: -------------------------------------------------------------------------------- 1 | export { Splide as default } from '../core/Splide/Splide'; 2 | -------------------------------------------------------------------------------- /src/js/build/renderer.ts: -------------------------------------------------------------------------------- 1 | export { SplideRenderer as default } from '../renderer/SplideRenderer/SplideRenderer'; 2 | -------------------------------------------------------------------------------- /src/js/components/Arrows/path.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The namespace for SVG elements. 3 | */ 4 | export const XML_NAME_SPACE = 'http://www.w3.org/2000/svg'; 5 | 6 | /** 7 | * The arrow path. 8 | */ 9 | export const PATH = 'm15.5 0.932-4.3 4.38 14.5 14.6-14.5 14.5 4.3 4.4 14.6-14.6 4.4-4.3-4.4-4.4-14.6-14.6z'; 10 | 11 | /** 12 | * SVG width and height. 13 | */ 14 | export const SIZE = 40; 15 | -------------------------------------------------------------------------------- /src/js/components/Arrows/test/loop.test.ts: -------------------------------------------------------------------------------- 1 | import { init } from '../../../test'; 2 | 3 | 4 | describe.each( [ [ 'loop' ], [ 'rewind' ] ] )( 'Arrows in "%s" mode', ( mode: string ) => { 5 | const type = mode === 'loop' ? 'loop' : 'slide'; 6 | const rewind = mode === 'rewind'; 7 | const splide = init( { arrows: true, type, rewind, speed: 0 } ); 8 | const { Arrows } = splide.Components; 9 | const { next, prev } = Arrows.arrows; 10 | const { i18n } = splide.options; 11 | 12 | test( 'should not disable arrows.', () => { 13 | splide.go( 0 ); 14 | 15 | expect( prev.disabled ).toBe( false ); 16 | expect( next.disabled ).toBe( false ); 17 | 18 | splide.go( splide.length - 1 ); 19 | 20 | expect( prev.disabled ).toBe( false ); 21 | expect( next.disabled ).toBe( false ); 22 | } ); 23 | 24 | test( 'should change the aria-label on the first or last slide.', () => { 25 | splide.go( 0 ); 26 | 27 | expect( prev.getAttribute( 'aria-label' ) ).toBe( i18n.last ); 28 | expect( next.getAttribute( 'aria-label' ) ).toBe( i18n.next ); 29 | 30 | splide.go( splide.length - 1 ); 31 | 32 | expect( prev.getAttribute( 'aria-label' ) ).toBe( i18n.prev ); 33 | expect( next.getAttribute( 'aria-label' ) ).toBe( i18n.first ); 34 | } ); 35 | } ); 36 | -------------------------------------------------------------------------------- /src/js/components/Arrows/test/slide.test.ts: -------------------------------------------------------------------------------- 1 | import { EVENT_MOVED } from '../../../constants/events'; 2 | import { fire, init } from '../../../test'; 3 | 4 | 5 | describe( 'Arrows', () => { 6 | const splide = init( { arrows: true, speed: 0 } ); 7 | const { Arrows } = splide.Components; 8 | const { next, prev } = Arrows.arrows; 9 | const { i18n } = splide.options; 10 | 11 | test( 'can generate arrows.', () => { 12 | expect( prev instanceof HTMLButtonElement ).toBe( true ); 13 | expect( next instanceof HTMLButtonElement ).toBe( true ); 14 | } ); 15 | 16 | test( 'can navigate the slider.', () => { 17 | fire( next, 'click' ); 18 | expect( splide.index ).toBe( 1 ); 19 | 20 | fire( next, 'click' ); 21 | expect( splide.index ).toBe( 2 ); 22 | 23 | fire( prev, 'click' ); 24 | expect( splide.index ).toBe( 1 ); 25 | 26 | fire( prev, 'click' ); 27 | expect( splide.index ).toBe( 0 ); 28 | } ); 29 | 30 | test( 'can disable arrows if there is no slide to go to.', () => { 31 | expect( prev.disabled ).toBe( true ); 32 | expect( next.disabled ).toBe( false ); 33 | 34 | // Go to the last slide. 35 | splide.go( splide.length - 1 ); 36 | 37 | expect( prev.disabled ).toBe( false ); 38 | expect( next.disabled ).toBe( true ); 39 | } ); 40 | 41 | test( 'should not disable arrows if the slider position is not approximately same with each limit.', () => { 42 | splide.go( 0 ); 43 | 44 | expect( prev.disabled ).toBe( true ); 45 | expect( next.disabled ).toBe( false ); 46 | 47 | splide.Components.Move.translate( -10 ); 48 | splide.emit( EVENT_MOVED ); 49 | 50 | // Index should be still 0 51 | expect( splide.index ).toBe( 0 ); 52 | 53 | expect( prev.disabled ).toBe( false ); 54 | expect( next.disabled ).toBe( false ); 55 | } ); 56 | 57 | test( 'can update aria attributes.', () => { 58 | expect( prev.getAttribute( 'aria-label' ) ).toBe( i18n.prev ); 59 | expect( next.getAttribute( 'aria-label' ) ).toBe( i18n.next ); 60 | } ); 61 | } ); 62 | -------------------------------------------------------------------------------- /src/js/components/Autoplay/constants.ts: -------------------------------------------------------------------------------- 1 | import { DATA_ATTRIBUTE } from '../../constants/project'; 2 | 3 | 4 | /** 5 | * The data attribute for the autoplay interval duration. 6 | * 7 | * @since 3.5.0 8 | */ 9 | export const INTERVAL_DATA_ATTRIBUTE = `${ DATA_ATTRIBUTE }-interval`; 10 | -------------------------------------------------------------------------------- /src/js/components/Controller/test/getEnd.test.ts: -------------------------------------------------------------------------------- 1 | import { init } from '../../../test'; 2 | 3 | 4 | describe( 'Controller#getEnd()', () => { 5 | test( 'can return the end index.', () => { 6 | const splide1 = init( { perPage: 3 }, { length: 4 } ); 7 | expect( splide1.Components.Controller.getEnd() ).toBe( 1 ); 8 | 9 | const splide2 = init( { perPage: 3 }, { length: 5 } ); 10 | expect( splide2.Components.Controller.getEnd() ).toBe( 2 ); 11 | 12 | const splide3 = init( { perPage: 3 }, { length: 6 } ); 13 | expect( splide3.Components.Controller.getEnd() ).toBe( 3 ); 14 | } ); 15 | 16 | test( 'should return length - 1 if the focus option is available.', () => { 17 | const splide = init( { focus: 'center', perPage: 3 }, { length: 4 } ); 18 | expect( splide.Components.Controller.getEnd() ).toBe( splide.length - 1 ); 19 | } ); 20 | 21 | test( 'should return length - 1 if the perMove option is available in the loop mode.', () => { 22 | const splide = init( { type: 'loop', perMove: 3 }, { length: 4 } ); 23 | expect( splide.Components.Controller.getEnd() ).toBe( splide.length - 1 ); 24 | } ); 25 | 26 | test( 'should return length - 1 if the perPage option is 1.', () => { 27 | const splide = init(); 28 | expect( splide.Components.Controller.getEnd() ).toBe( splide.length - 1 ); 29 | } ); 30 | } ); 31 | -------------------------------------------------------------------------------- /src/js/components/Controller/test/isBusy.test.ts: -------------------------------------------------------------------------------- 1 | import { fire, init } from '../../../test'; 2 | 3 | 4 | describe( 'Controller#isBusy', () => { 5 | test( 'can check if the slider is moving or not.', () => { 6 | const splide = init( { width: 200, height: 100, waitForTransition: true } ); 7 | const { Controller, Move } = splide.Components; 8 | 9 | expect( Controller.isBusy() ).toBe( false ); 10 | 11 | Move.move( 1, 1, -1 ); 12 | expect( Controller.isBusy() ).toBe( true ); 13 | 14 | fire( splide.Components.Elements.list, 'transitionend' ); 15 | expect( Controller.isBusy() ).toBe( false ); 16 | } ); 17 | 18 | test( 'can check if the slider is being scrolled or not.', () => { 19 | const splide = init( { width: 200, height: 100, waitForTransition: true } ); 20 | const { Controller, Scroll } = splide.Components; 21 | 22 | expect( Controller.isBusy() ).toBe( false ); 23 | 24 | Scroll.scroll( 10, 10 ); 25 | expect( Controller.isBusy() ).toBe( true ); 26 | 27 | Scroll.cancel(); 28 | expect( Controller.isBusy() ).toBe( false ); 29 | } ); 30 | 31 | test( 'should always return true if `waitForTransition` is false.', () => { 32 | const splide = init( { width: 200, height: 100, waitForTransition: false } ); 33 | const { Controller, Move, Scroll } = splide.Components; 34 | 35 | expect( Controller.isBusy() ).toBe( false ); 36 | 37 | Move.move( 1, 1, -1 ); 38 | expect( Controller.isBusy() ).toBe( false ); 39 | 40 | Move.cancel(); 41 | expect( Controller.isBusy() ).toBe( false ); 42 | 43 | Scroll.scroll( 10, 10 ); 44 | expect( Controller.isBusy() ).toBe( false ); 45 | 46 | Scroll.cancel(); 47 | expect( Controller.isBusy() ).toBe( false ); 48 | } ); 49 | } ); -------------------------------------------------------------------------------- /src/js/components/Controller/test/loop.test.ts: -------------------------------------------------------------------------------- 1 | import { init } from '../../../test'; 2 | 3 | 4 | describe( 'Controller#go()', () => { 5 | test( 'can loop the slider.', () => { 6 | const splide = init( { type: 'loop', speed: 0 } ); 7 | 8 | splide.go( '<' ); 9 | expect( splide.index ).toBe( splide.length - 1 ); 10 | 11 | splide.go( '>' ); 12 | expect( splide.index ).toBe( 0 ); 13 | 14 | splide.go( '-' ); 15 | expect( splide.index ).toBe( splide.length - 1 ); 16 | 17 | splide.go( '+' ); 18 | expect( splide.index ).toBe( 0 ); 19 | } ); 20 | 21 | test( 'can loop the slider, using the index of the last page.', () => { 22 | // The length is 10 and the last page only contains the slide 9. 23 | const splide = init( { type: 'loop', perPage: 3, speed: 0 } ); 24 | 25 | splide.go( '<' ); 26 | expect( splide.index ).toBe( 9 ); // 9, 0, 1 27 | 28 | splide.go( '<' ); 29 | expect( splide.index ).toBe( 6 ); // 6, 7, 8 30 | 31 | splide.go( '>' ); 32 | expect( splide.index ).toBe( 7 ); // 7, 8, 9 33 | 34 | splide.go( '>' ); 35 | expect( splide.index ).toBe( 0 ); 36 | } ); 37 | } ); 38 | -------------------------------------------------------------------------------- /src/js/components/Controller/test/rewind.test.ts: -------------------------------------------------------------------------------- 1 | import { init } from '../../../test'; 2 | 3 | 4 | describe( 'Controller#go()', () => { 5 | test( 'can rewind the slider.', () => { 6 | const splide = init( { rewind: true, speed: 0 } ); 7 | 8 | splide.go( '<' ); 9 | expect( splide.index ).toBe( splide.length - 1 ); 10 | 11 | splide.go( '>' ); 12 | expect( splide.index ).toBe( 0 ); 13 | 14 | splide.go( '-' ); 15 | expect( splide.index ).toBe( splide.length - 1 ); 16 | 17 | splide.go( '+' ); 18 | expect( splide.index ).toBe( 0 ); 19 | } ); 20 | 21 | test( 'can rewind the slider, using the end index.', () => { 22 | // The end index is 1. 23 | const splide = init( { rewind: true, perPage: 3, speed: 0 }, { length: 4 } ); 24 | 25 | splide.go( '<' ); 26 | expect( splide.index ).toBe( 1 ); 27 | 28 | splide.go( '>' ); 29 | expect( splide.index ).toBe( 0 ); 30 | } ); 31 | } ); 32 | -------------------------------------------------------------------------------- /src/js/components/Controller/test/scroll.test.ts: -------------------------------------------------------------------------------- 1 | import { init, wait } from '../../../test'; 2 | import { BOUNCE_DURATION } from '../../Scroll/constants'; 3 | 4 | 5 | describe( 'Controller#scroll()', () => { 6 | test( 'can scroll the carousel.', done => { 7 | const splide = init(); 8 | const { scroll } = splide.Components.Controller; 9 | 10 | scroll( -100, 100, false, () => { 11 | expect( splide.Components.Move.getPosition() ).toBe( -100 ); 12 | done(); 13 | } ); 14 | } ); 15 | 16 | test( 'can update the index after scroll.', async () => { 17 | const splide = init( { width: 100 } ); 18 | const { scroll } = splide.Components.Controller; 19 | 20 | scroll( -100, 100 ); 21 | await wait( 200 ); 22 | expect( splide.index ).toBe( 1 ); 23 | 24 | scroll( -200, 100 ); 25 | await wait( 200 ); 26 | expect( splide.index ).toBe( 2 ); 27 | } ); 28 | 29 | test( 'can update the index after the carousel exceeds bounds.', async () => { 30 | const splide = init( { width: 100 } ); 31 | const { scroll } = splide.Components.Controller; 32 | 33 | scroll( -100, 100 ); 34 | await wait( 200 ); 35 | expect( splide.index ).toBe( 1 ); 36 | 37 | // Make the carousel exceed the left limit. 38 | scroll( 100, 100 ); 39 | await wait( 100 + BOUNCE_DURATION + 100 ); 40 | expect( splide.index ).toBe( 0 ); 41 | } ); 42 | } ); 43 | -------------------------------------------------------------------------------- /src/js/components/Controller/test/toIndex.test.ts: -------------------------------------------------------------------------------- 1 | import { init } from '../../../test'; 2 | 3 | 4 | describe( 'Controller#toIndex()', () => { 5 | test( 'can convert the page index to the slide index.', () => { 6 | const splide = init( { perPage: 3 } ); 7 | const { toIndex } = splide.Components.Controller; 8 | 9 | expect( toIndex( 0 ) ).toBe( 0 ); 10 | expect( toIndex( 1 ) ).toBe( 3 ); 11 | expect( toIndex( 2 ) ).toBe( 6 ); 12 | } ); 13 | 14 | test( 'can convert the page index with respecting the end index.', () => { 15 | const splide = init( { perPage: 3 }, { length: 4 } ); 16 | const { toIndex } = splide.Components.Controller; 17 | 18 | expect( toIndex( 0 ) ).toBe( 0 ); 19 | expect( toIndex( 1 ) ).toBe( 1 ); 20 | } ); 21 | } ); 22 | -------------------------------------------------------------------------------- /src/js/components/Controller/test/toPage.test.ts: -------------------------------------------------------------------------------- 1 | import { init } from '../../../test'; 2 | 3 | 4 | describe( 'Controller#toPage()', () => { 5 | test( 'can convert the slide index to the page index.', () => { 6 | const splide = init( { perPage: 3 } ); 7 | const { toPage } = splide.Components.Controller; 8 | 9 | expect( toPage( 0 ) ).toBe( 0 ); 10 | expect( toPage( 1 ) ).toBe( 0 ); 11 | expect( toPage( 2 ) ).toBe( 0 ); 12 | 13 | expect( toPage( 3 ) ).toBe( 1 ); 14 | expect( toPage( 4 ) ).toBe( 1 ); 15 | expect( toPage( 5 ) ).toBe( 1 ); 16 | } ); 17 | 18 | test( 'can convert the slide index to the page index with respecting the end index.', () => { 19 | const splide = init( { perPage: 3 }, { length: 4 } ); 20 | const { toPage } = splide.Components.Controller; 21 | 22 | expect( toPage( 0 ) ).toBe( 0 ); 23 | expect( toPage( 1 ) ).toBe( 1 ); 24 | expect( toPage( 2 ) ).toBe( 1 ); 25 | expect( toPage( 3 ) ).toBe( 1 ); 26 | } ); 27 | 28 | test( 'should return the slide index as is if the focus option is available.', () => { 29 | const splide = init( { focus: 'center', perPage: 3 } ); 30 | const { toPage } = splide.Components.Controller; 31 | 32 | expect( toPage( 0 ) ).toBe( 0 ); 33 | expect( toPage( 1 ) ).toBe( 1 ); 34 | expect( toPage( 2 ) ).toBe( 2 ); 35 | expect( toPage( 3 ) ).toBe( 3 ); 36 | } ); 37 | } ); 38 | -------------------------------------------------------------------------------- /src/js/components/Direction/test/general.test.ts: -------------------------------------------------------------------------------- 1 | import { LTR, RTL, TTB } from '../../../constants/directions'; 2 | import { init } from '../../../test'; 3 | 4 | 5 | describe( 'Direction', () => { 6 | const splide = init(); 7 | const { options } = splide; 8 | const { resolve, orient } = splide.Components.Direction; 9 | 10 | test( 'can provide property names for LTR.', () => { 11 | options.direction = LTR; 12 | 13 | expect( resolve( 'marginRight' ) ).toBe( 'marginRight' ); 14 | expect( resolve( 'width' ) ).toBe( 'width' ); 15 | expect( resolve( 'paddingLeft' ) ).toBe( 'paddingLeft' ); 16 | } ); 17 | 18 | test( 'can provide property names for TTB.', () => { 19 | options.direction = TTB; 20 | 21 | expect( resolve( 'marginRight' ) ).toBe( 'marginBottom' ); 22 | expect( resolve( 'width' ) ).toBe( 'height' ); 23 | expect( resolve( 'paddingLeft' ) ).toBe( 'paddingTop' ); 24 | } ); 25 | 26 | test( 'can provide property names for RTL.', () => { 27 | options.direction = RTL; 28 | 29 | expect( resolve( 'marginRight' ) ).toBe( 'marginLeft' ); 30 | expect( resolve( 'width' ) ).toBe( 'width' ); 31 | expect( resolve( 'paddingLeft' ) ).toBe( 'paddingRight' ); 32 | } ); 33 | 34 | test( 'can provide same property names for LTR and RTL if the axisOnly is true.', () => { 35 | options.direction = RTL; 36 | 37 | expect( resolve( 'marginRight', true ) ).toBe( 'marginRight' ); 38 | expect( resolve( 'paddingLeft', true ) ).toBe( 'paddingLeft' ); 39 | } ); 40 | 41 | test( 'can orient the provided value towards the current direction.', () => { 42 | options.direction = LTR; 43 | expect( orient( 1 ) ).toBe( -1 ); 44 | 45 | options.direction = TTB; 46 | expect( orient( 1 ) ).toBe( -1 ); 47 | 48 | options.direction = RTL; 49 | expect( orient( 1 ) ).toBe( 1 ); 50 | } ); 51 | } ); 52 | -------------------------------------------------------------------------------- /src/js/components/Drag/constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The power of the friction. 3 | * 4 | * @since 3.0.0 5 | */ 6 | export const FRICTION = 5; 7 | 8 | /** 9 | * If the user stops dragging for this duration with keeping the pointer down, updates the base coord and time. 10 | * 11 | * @since 3.0.0 12 | */ 13 | export const LOG_INTERVAL = 200; 14 | 15 | /** 16 | * Start events for dragging. 17 | * 18 | * @since 3.0.0 19 | */ 20 | export const POINTER_DOWN_EVENTS = 'touchstart mousedown'; 21 | 22 | /** 23 | * Update events for dragging. 24 | * 25 | * @since 3.0.0 26 | */ 27 | export const POINTER_MOVE_EVENTS = 'touchmove mousemove'; 28 | 29 | /** 30 | * End events for dragging. 31 | * The `click` event is required because the browser sometimes dispatches `drag` events instead of `mouse`. 32 | * 33 | * @since 3.0.0 34 | */ 35 | export const POINTER_UP_EVENTS = 'touchend touchcancel mouseup click'; 36 | -------------------------------------------------------------------------------- /src/js/components/Elements/test/focus.test.ts: -------------------------------------------------------------------------------- 1 | import { CLASS_FOCUS_IN } from '../../../constants/classes'; 2 | import { fire, init } from '../../../test'; 3 | 4 | 5 | describe( 'Focus', () => { 6 | test( 'can add the status class to the root when focus comes into it by a key.', () => { 7 | const splide = init( {}, { arrows: true } ); 8 | 9 | fire( document, 'keydown' ); 10 | fire( splide.root, 'focusin' ); 11 | 12 | expect( splide.root.classList.contains( CLASS_FOCUS_IN ) ).toBe( true ); 13 | } ); 14 | 15 | test( 'can remove the status class from the root when detecting pointerdown.', () => { 16 | const splide = init( {}, { arrows: true } ); 17 | 18 | fire( document, 'keydown' ); 19 | fire( splide.root, 'focusin' ); 20 | 21 | expect( splide.root.classList.contains( CLASS_FOCUS_IN ) ).toBe( true ); 22 | 23 | fire( splide.root, 'mousedown' ); 24 | fire( splide.root, 'focusin' ); 25 | 26 | expect( splide.root.classList.contains( CLASS_FOCUS_IN ) ).toBe( false ); 27 | } ); 28 | 29 | test( 'should not add the status class when focus comes into the root by pointing devices.', () => { 30 | const splide = init( {}, { arrows: true } ); 31 | 32 | fire( document, 'mousedown' ); 33 | fire( splide.root, 'focusin' ); 34 | 35 | expect( splide.root.classList.contains( CLASS_FOCUS_IN ) ).toBe( false ); 36 | } ); 37 | } ); 38 | -------------------------------------------------------------------------------- /src/js/components/Layout/test/general.test.ts: -------------------------------------------------------------------------------- 1 | import { EVENT_RESIZE } from '../../../constants/events'; 2 | import { init } from '../../../test'; 3 | 4 | 5 | describe( 'Layout', () => { 6 | test( 'can set the max width of the slider.', () => { 7 | const splide = init( { width: 100 } ); 8 | 9 | expect( splide.root.style.maxWidth ).toBe( '100px' ); 10 | splide.destroy(); 11 | } ); 12 | 13 | test( 'should init the component again when options are updated.', () => { 14 | const splide = init( { width: 100 } ); 15 | const style = splide.root.style; 16 | 17 | expect( style.maxWidth ).toBe( '100px' ); 18 | 19 | splide.options = { width: 200 }; 20 | 21 | expect( style.maxWidth ).toBe( '200px' ); 22 | 23 | splide.destroy(); 24 | } ); 25 | 26 | test( 'should init the component again when the splide is refreshed.', () => { 27 | const splide = init( { width: 100 } ); 28 | const style = splide.root.style; 29 | 30 | expect( style.maxWidth ).toBe( '100px' ); 31 | 32 | splide.options = { width: 200 }; 33 | splide.refresh(); 34 | 35 | expect( style.maxWidth ).toBe( '200px' ); 36 | 37 | splide.destroy(); 38 | } ); 39 | 40 | test( 'should update the slide height when the window is resized.', () => { 41 | const splide = init( { width: 1000, heightRatio: 0.5 } ); 42 | const style = splide.Components.Elements.slides[ 0 ].style; 43 | 44 | expect( style.height ).toBe( '500px' ); 45 | 46 | splide.options = { heightRatio: 0.2 }; 47 | splide.emit( EVENT_RESIZE ); 48 | 49 | expect( style.height ).toBe( '200px' ); 50 | 51 | splide.destroy(); 52 | } ); 53 | } ); 54 | -------------------------------------------------------------------------------- /src/js/components/Layout/test/rtl.test.ts: -------------------------------------------------------------------------------- 1 | import { RTL } from '../../../constants/directions'; 2 | import { init } from '../../../test'; 3 | 4 | 5 | describe( 'Layout in the RTL mode', () => { 6 | test( 'can set margin as a gap.', () => { 7 | const splide = init( { direction: RTL, gap: '2rem' } ); 8 | 9 | expect( splide.Components.Elements.slides[ 0 ].style.marginLeft ).toBe( '2rem' ); 10 | expect( splide.Components.Elements.slides[ 1 ].style.marginLeft ).toBe( '2rem' ); 11 | splide.destroy(); 12 | } ); 13 | } ); 14 | -------------------------------------------------------------------------------- /src/js/components/LazyLoad/constants.ts: -------------------------------------------------------------------------------- 1 | import { DATA_ATTRIBUTE } from '../../constants/project'; 2 | 3 | 4 | /** 5 | * The data attribute for the src value. 6 | * 7 | * @since 3.0.0 8 | */ 9 | export const SRC_DATA_ATTRIBUTE = `${ DATA_ATTRIBUTE }-lazy`; 10 | 11 | /** 12 | * The data attribute for the srcset value. 13 | * 14 | * @since 3.0.0 15 | */ 16 | export const SRCSET_DATA_ATTRIBUTE = `${ SRC_DATA_ATTRIBUTE }-srcset`; 17 | 18 | /** 19 | * The selector string for images to load. 20 | * 21 | * @since 3.0.0 22 | */ 23 | export const IMAGE_SELECTOR = `[${ SRC_DATA_ATTRIBUTE }], [${ SRCSET_DATA_ATTRIBUTE }]`; 24 | -------------------------------------------------------------------------------- /src/js/components/LazyLoad/test/sequential.test.ts: -------------------------------------------------------------------------------- 1 | import { fire, init } from '../../../test'; 2 | import { URL } from '../../../test/fixtures/constants'; 3 | 4 | 5 | describe( 'LazyLoad in the `sequential` mode', () => { 6 | test( 'can sequentially load images.', () => { 7 | init( { lazyLoad: 'sequential' }, { src: false, dataSrc: true } ); 8 | const images = document.getElementsByTagName( 'img' ); 9 | 10 | expect( images[ 0 ].src ).toBe( `${ URL }/0.jpg` ); 11 | expect( images[ 1 ].src ).toBe( '' ); 12 | expect( images[ 2 ].src ).toBe( '' ); 13 | 14 | fire( images[ 0 ], 'load' ); 15 | expect( images[ 1 ].src ).toBe( `${ URL }/1.jpg` ); 16 | expect( images[ 2 ].src ).toBe( '' ); 17 | 18 | fire( images[ 1 ], 'load' ); 19 | expect( images[ 2 ].src ).toBe( `${ URL }/2.jpg` ); 20 | } ); 21 | 22 | test( 'should load the next image if the current image throws error to load.', () => { 23 | init( { lazyLoad: 'sequential' }, { src: false, dataSrc: true } ); 24 | const images = document.getElementsByTagName( 'img' ); 25 | 26 | expect( images[ 0 ].src ).toBe( `${ URL }/0.jpg` ); 27 | expect( images[ 1 ].src ).toBe( '' ); 28 | expect( images[ 2 ].src ).toBe( '' ); 29 | 30 | fire( images[ 0 ], 'error' ); 31 | expect( images[ 1 ].src ).toBe( `${ URL }/1.jpg` ); 32 | 33 | fire( images[ 1 ], 'error' ); 34 | expect( images[ 2 ].src ).toBe( `${ URL }/2.jpg` ); 35 | } ); 36 | } ); 37 | -------------------------------------------------------------------------------- /src/js/components/Move/test/move.test.ts: -------------------------------------------------------------------------------- 1 | import { EVENT_MOVE, EVENT_MOVED } from '../../../constants/events'; 2 | import { IDLE, MOVING } from '../../../constants/states'; 3 | import { fire, init } from '../../../test'; 4 | 5 | 6 | describe( 'Move#move()', () => { 7 | test( 'can move the slider with the transition component.', () => { 8 | const splide = init( { width: 200, height: 100 } ); 9 | const { Move } = splide.Components; 10 | const { list } = splide.Components.Elements; 11 | 12 | Move.move( 1, 1, -1 ); 13 | fire( list, 'transitionend' ); 14 | expect( list.style.transform ).toBe( 'translateX(-200px)' ); 15 | 16 | Move.move( 2, 2, -1 ); 17 | fire( list, 'transitionend' ); 18 | expect( list.style.transform ).toBe( 'translateX(-400px)' ); 19 | } ); 20 | 21 | test( 'can change the state.', () => { 22 | const splide = init( { width: 200, height: 100 } ); 23 | const { Move } = splide.Components; 24 | const { list } = splide.Components.Elements; 25 | 26 | Move.move( 1, 1, -1 ); 27 | expect( splide.state.is( MOVING ) ).toBe( true ); 28 | 29 | fire( list, 'transitionend' ); 30 | expect( splide.state.is( IDLE ) ).toBe( true ); 31 | } ); 32 | 33 | test( 'can emit events.', done => { 34 | const splide = init( { width: 200, height: 100 } ); 35 | const { Move } = splide.Components; 36 | const { list } = splide.Components.Elements; 37 | 38 | splide.on( EVENT_MOVE, ( index, prev, dest ) => { 39 | expect( index ).toBe( 2 ); 40 | expect( prev ).toBe( 1 ); 41 | expect( dest ).toBe( 3 ); 42 | } ); 43 | 44 | splide.on( EVENT_MOVED, ( index, prev, dest ) => { 45 | expect( index ).toBe( 2 ); 46 | expect( prev ).toBe( 1 ); 47 | expect( dest ).toBe( 3 ); 48 | 49 | done(); 50 | } ); 51 | 52 | Move.move( 3, 2, 1 ); 53 | fire( list, 'transitionend' ); 54 | } ); 55 | } ); 56 | -------------------------------------------------------------------------------- /src/js/components/Pagination/test/placeholder.test.ts: -------------------------------------------------------------------------------- 1 | import { init } from '../../../test'; 2 | import { fail } from 'assert'; 3 | 4 | 5 | describe( 'Pagination', () => { 6 | const html = ` 7 |
8 |
    9 | 10 |
    11 |
      12 |
    • Slide 01
    • 13 |
    • Slide 02
    • 14 |
    • Slide 03
    • 15 |
    16 |
    17 |
    18 | `; 19 | 20 | test( 'can use a placeholder if provided.', () => { 21 | const splide = init( {}, { html } ); 22 | const placeholder = document.querySelector( '.pagination-placeholder' ); 23 | 24 | expect( placeholder ).not.toBeUndefined(); 25 | expect( splide.Components.Elements.pagination === placeholder ).toBe( true ); 26 | 27 | const { items } = splide.Components.Pagination; 28 | const { children } = placeholder; 29 | 30 | expect( items.length === children.length ).toBe( true ); 31 | expect( items[ 0 ].li === children[ 0 ] ).toBe( true ); 32 | } ); 33 | 34 | test( 'should toggle a placeholder according to `pagination` options.', () => { 35 | const splide = init( { pagination: false }, { html } ); 36 | const placeholder = document.querySelector( '.pagination-placeholder' ); 37 | 38 | if ( placeholder instanceof HTMLElement ) { 39 | expect( placeholder.style.display ).toBe( 'none' ); 40 | 41 | // Update options to show the pagination. 42 | splide.options = { pagination: true }; 43 | expect( placeholder.style.display ).toBe( '' ); 44 | } else { 45 | fail(); 46 | } 47 | } ); 48 | } ); 49 | -------------------------------------------------------------------------------- /src/js/components/Scroll/constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Triggers the bounce effect when the diff becomes less than this value. 3 | * 4 | * @since 3.0.0 5 | */ 6 | export const BOUNCE_DIFF_THRESHOLD = 10; 7 | 8 | /** 9 | * The duration of the bounce effect. 10 | * 11 | * @since 3.0.0 12 | */ 13 | export const BOUNCE_DURATION = 600; 14 | 15 | /** 16 | * The friction factor. 17 | * 18 | * @since 3.0.0 19 | */ 20 | export const FRICTION_FACTOR = 0.6; 21 | 22 | /** 23 | * The velocity to calculate the scroll duration. 24 | * 25 | * @since 3.0.0 26 | */ 27 | export const BASE_VELOCITY = 1.5; 28 | 29 | /** 30 | * The minimum duration of scroll. 31 | * 32 | * @since 3.0.0 33 | */ 34 | export const MIN_DURATION = 800; 35 | -------------------------------------------------------------------------------- /src/js/components/Slides/test/filter.test.ts: -------------------------------------------------------------------------------- 1 | import { CLASS_SLIDE } from '../../../constants/classes'; 2 | import { init } from '../../../test'; 3 | 4 | 5 | describe( 'Slides#filter()', () => { 6 | const splide = init(); 7 | const { Slides } = splide.Components; 8 | const all = Slides.get(); 9 | 10 | test( 'can filter slides by the index.', () => { 11 | expect( Slides.filter( 2 )[ 0 ] ).toBe( Slides.get()[ 2 ] ); 12 | } ); 13 | 14 | test( 'can filter slides by indices.', () => { 15 | expect( Slides.filter( [ 0, 2, 4 ] ) ).toEqual( [ all[ 0 ], all[ 2 ], all[ 4 ] ] ); 16 | } ); 17 | 18 | test( 'can filter slides by the CSS selector.', () => { 19 | const first = Slides.filter( `.${ CLASS_SLIDE }:first-child` )[ 0 ]; 20 | const last = Slides.filter( `.${ CLASS_SLIDE }:last-child` )[ 0 ]; 21 | 22 | expect( first ).toBe( all[ 0 ] ); 23 | expect( last ).toBe( all[ all.length - 1 ] ); 24 | } ); 25 | 26 | test( 'can filter slides by the predicate function.', () => { 27 | const filtered = Slides.filter( ( Slide, index ) => { 28 | return index < 5; 29 | } ); 30 | 31 | expect( filtered.length ).toBe( 5 ); 32 | expect( filtered[ filtered.length - 1 ] ).toBe( all[ filtered.length - 1 ] ); 33 | } ); 34 | } ); 35 | -------------------------------------------------------------------------------- /src/js/components/Slides/test/remove.test.ts: -------------------------------------------------------------------------------- 1 | import { init } from '../../../test'; 2 | 3 | 4 | describe( 'Slides#remove()', () => { 5 | test( 'can remove a slide at the specified index.', () => { 6 | const splide = init(); 7 | const { Slides } = splide.Components; 8 | const Slide1 = Slides.getAt( 1 ); 9 | const Slide2 = Slides.getAt( 2 ); 10 | const length = Slides.getLength(); 11 | 12 | Slides.remove( 1 ); 13 | 14 | expect( Slides.getLength() ).toBe( length - 1 ); 15 | expect( Slides.getAt( 1 ).slide ).not.toBe( Slide1.slide ); 16 | expect( Slides.getAt( 1 ).slide ).toBe( Slide2.slide ); 17 | } ); 18 | 19 | test( 'can remove slides at the specified indices.', () => { 20 | const splide = init(); 21 | const { Slides } = splide.Components; 22 | const Slide4 = Slides.getAt( 4 ); 23 | const length = Slides.getLength(); 24 | 25 | Slides.remove( [ 1, 2, 3 ] ); 26 | 27 | expect( Slides.getLength() ).toBe( length - 3 ); 28 | expect( Slides.getAt( 1 ).slide ).toBe( Slide4.slide ); 29 | } ); 30 | 31 | test( 'can remove slides by a selector.', () => { 32 | const splide = init(); 33 | const { Slides } = splide.Components; 34 | const Slide3 = Slides.getAt( 3 ); 35 | const length = Slides.getLength(); 36 | 37 | Slides.remove( 'li:nth-child( 1 ), li:nth-child( 2 )' ); 38 | 39 | expect( Slides.getLength() ).toBe( length - 2 ); 40 | expect( Slides.getAt( 1 ).slide ).toBe( Slide3.slide ); 41 | } ); 42 | 43 | test( 'can remove slides by a predicate function.', () => { 44 | const splide = init(); 45 | const { Slides } = splide.Components; 46 | const Slide3 = Slides.getAt( 3 ); 47 | const length = Slides.getLength(); 48 | 49 | Slides.remove( Slide => Slide.index < 3 ); 50 | 51 | expect( Slides.getLength() ).toBe( length - 3 ); 52 | expect( Slides.getAt( 0 ).slide ).toBe( Slide3.slide ); 53 | } ); 54 | } ); 55 | -------------------------------------------------------------------------------- /src/js/components/Sync/test/navigation.test.ts: -------------------------------------------------------------------------------- 1 | import { fire, init, keydown } from '../../../test'; 2 | 3 | 4 | describe( 'Sync#navigate()', () => { 5 | test( 'can make slides clickable.', () => { 6 | const primary = init( { speed: 0 }, { id: 'primary', mount: false } ); 7 | const secondary = init( { speed: 0, isNavigation: true }, { id: 'secondary', insertHtml: true, mount: false } ); 8 | 9 | primary.sync( secondary ).mount(); 10 | secondary.mount(); 11 | 12 | const Slides = secondary.Components.Slides.get(); 13 | 14 | fire( Slides[ 1 ].slide, 'click' ); 15 | 16 | expect( primary.index ).toBe( 1 ); 17 | expect( secondary.index ).toBe( 1 ); 18 | 19 | fire( Slides[ 5 ].slide, 'click' ); 20 | 21 | expect( primary.index ).toBe( 5 ); 22 | expect( secondary.index ).toBe( 5 ); 23 | } ); 24 | 25 | test( 'can make slides receive key inputs.', () => { 26 | const primary = init( { speed: 0 }, { id: 'primary', mount: false } ); 27 | const secondary = init( { speed: 0, isNavigation: true }, { id: 'secondary', insertHtml: true, mount: false } ); 28 | 29 | primary.sync( secondary ).mount(); 30 | secondary.mount(); 31 | 32 | const Slides = secondary.Components.Slides.get(); 33 | 34 | Slides[ 1 ].slide.focus(); 35 | keydown( 'Enter', Slides[ 1 ].slide ); 36 | 37 | expect( primary.index ).toBe( 1 ); 38 | expect( secondary.index ).toBe( 1 ); 39 | 40 | Slides[ 5 ].slide.focus(); 41 | keydown( ' ', Slides[ 5 ].slide ); 42 | 43 | expect( primary.index ).toBe( 5 ); 44 | expect( secondary.index ).toBe( 5 ); 45 | 46 | Slides[ 3 ].slide.focus(); 47 | keydown( 'Spacebar', Slides[ 3 ].slide ); 48 | 49 | expect( primary.index ).toBe( 3 ); 50 | expect( secondary.index ).toBe( 3 ); 51 | } ); 52 | } ); 53 | -------------------------------------------------------------------------------- /src/js/components/Wheel/test/general.test.ts: -------------------------------------------------------------------------------- 1 | import { fire, init } from '../../../test'; 2 | 3 | 4 | describe( 'Wheel', () => { 5 | test( 'can navigate the slider by the mouse wheel.', () => { 6 | const splide = init( { speed: 0, wheel: true } ); 7 | const { track } = splide.Components.Elements; 8 | 9 | fireCancelable( track, 'wheel', { deltaY: 100 } ); 10 | expect( splide.index ).toBe( 1 ); 11 | 12 | fireCancelable( track, 'wheel', { deltaY: 100 } ); 13 | expect( splide.index ).toBe( 2 ); 14 | 15 | fireCancelable( track, 'wheel', { deltaY: 100 } ); 16 | expect( splide.index ).toBe( 3 ); 17 | 18 | fireCancelable( track, 'wheel', { deltaY: -100 } ); 19 | expect( splide.index ).toBe( 2 ); 20 | 21 | fireCancelable( track, 'wheel', { deltaY: -100 } ); 22 | expect( splide.index ).toBe( 1 ); 23 | 24 | fireCancelable( track, 'wheel', { deltaY: -100 } ); 25 | expect( splide.index ).toBe( 0 ); 26 | } ); 27 | 28 | test( 'should be inactive if the `wheel` option is falsy.', () => { 29 | const splide = init( { speed: 0, wheel: false } ); 30 | const { track } = splide.Components.Elements; 31 | 32 | fireCancelable( track, 'wheel', { deltaY: 100 } ); 33 | expect( splide.index ).toBe( 0 ); 34 | 35 | fireCancelable( track, 'wheel', { deltaY: 100 } ); 36 | expect( splide.index ).toBe( 0 ); 37 | } ); 38 | } ); 39 | 40 | export function fireCancelable( elm: Element | Window, event: string, data: any = {} ): void { 41 | fire( elm, event, data, { cancelable: true } ); 42 | } 43 | 44 | -------------------------------------------------------------------------------- /src/js/components/Wheel/test/inertia.test.ts: -------------------------------------------------------------------------------- 1 | import { init } from '../../../test'; 2 | import { fireCancelable } from './general.test'; 3 | 4 | 5 | describe( 'Wheel', () => { 6 | test( 'should move the slider only when the delta is greater than the min threshold.', () => { 7 | const splide = init( { speed: 0, wheel: true, wheelMinThreshold: 50 } ); 8 | const { track } = splide.Components.Elements; 9 | 10 | fireCancelable( track, 'wheel', { deltaY: 49 } ); 11 | expect( splide.index ).toBe( 0 ); 12 | 13 | fireCancelable( track, 'wheel', { deltaY: 50 } ); 14 | expect( splide.index ).toBe( 0 ); 15 | 16 | fireCancelable( track, 'wheel', { deltaY: 51 } ); 17 | expect( splide.index ).toBe( 1 ); 18 | 19 | fireCancelable( track, 'wheel', { deltaY: -49 } ); 20 | expect( splide.index ).toBe( 1 ); 21 | 22 | fireCancelable( track, 'wheel', { deltaY: -50 } ); 23 | expect( splide.index ).toBe( 1 ); 24 | 25 | fireCancelable( track, 'wheel', { deltaY: -51 } ); 26 | expect( splide.index ).toBe( 0 ); 27 | } ); 28 | 29 | test( 'should not move the slider while the wheel component is sleeping.', () => { 30 | const splide = init( { speed: 0, wheel: true, wheelSleep: 500 } ); 31 | const { track } = splide.Components.Elements; 32 | 33 | fireCancelable( track, 'wheel', { deltaY: 100, timeStamp: 1000 } ); 34 | expect( splide.index ).toBe( 1 ); 35 | 36 | fireCancelable( track, 'wheel', { deltaY: 100, timeStamp: 1100 } ); 37 | expect( splide.index ).toBe( 1 ); 38 | 39 | fireCancelable( track, 'wheel', { deltaY: 100, timeStamp: 1500 } ); 40 | expect( splide.index ).toBe( 1 ); 41 | 42 | fireCancelable( track, 'wheel', { deltaY: 100, timeStamp: 1501 } ); 43 | expect( splide.index ).toBe( 2 ); 44 | } ); 45 | } ); 46 | 47 | -------------------------------------------------------------------------------- /src/js/components/index.ts: -------------------------------------------------------------------------------- 1 | export { Media } from './Media/Media'; 2 | export { Direction } from './Direction/Direction'; 3 | export { Elements } from './Elements/Elements'; 4 | export { Slides } from './Slides/Slides'; 5 | export { Layout } from './Layout/Layout'; 6 | export { Clones } from './Clones/Clones'; 7 | export { Move } from './Move/Move'; 8 | export { Controller } from './Controller/Controller'; 9 | export { Arrows } from './Arrows/Arrows'; 10 | export { Autoplay } from './Autoplay/Autoplay'; 11 | export { Cover } from './Cover/Cover'; 12 | export { Scroll } from './Scroll/Scroll'; 13 | export { Drag } from './Drag/Drag'; 14 | export { Keyboard } from './Keyboard/Keyboard'; 15 | export { LazyLoad } from './LazyLoad/LazyLoad'; 16 | export { Pagination } from './Pagination/Pagination'; 17 | export { Sync } from './Sync/Sync'; 18 | export { Wheel } from './Wheel/Wheel'; 19 | export { Live } from './Live/Live'; 20 | -------------------------------------------------------------------------------- /src/js/components/types.ts: -------------------------------------------------------------------------------- 1 | export type { MediaComponent } from './Media/Media'; 2 | export type { DirectionComponent } from './Direction/Direction'; 3 | export type { ElementsComponent } from './Elements/Elements'; 4 | export type { SlidesComponent } from './Slides/Slides'; 5 | export type { SlideComponent } from './Slides/Slide'; 6 | export type { LayoutComponent } from './Layout/Layout'; 7 | export type { ClonesComponent } from './Clones/Clones'; 8 | export type { MoveComponent } from './Move/Move'; 9 | export type { ControllerComponent } from './Controller/Controller'; 10 | export type { ArrowsComponent } from './Arrows/Arrows'; 11 | export type { AutoplayComponent } from './Autoplay/Autoplay'; 12 | export type { CoverComponent } from './Cover/Cover'; 13 | export type { ScrollComponent } from './Scroll/Scroll'; 14 | export type { DragComponent } from './Drag/Drag'; 15 | export type { KeyboardComponent } from './Keyboard/Keyboard'; 16 | export type { LazyLoadComponent } from './LazyLoad/LazyLoad'; 17 | export type { PaginationComponent } from './Pagination/Pagination'; 18 | export type { SyncComponent } from './Sync/Sync'; 19 | export type { WheelComponent } from './Wheel/Wheel'; 20 | export type { LiveComponent } from './Live/Live'; 21 | 22 | export type { PaginationData, PaginationItem } from './Pagination/Pagination'; 23 | -------------------------------------------------------------------------------- /src/js/constants/arrows.ts: -------------------------------------------------------------------------------- 1 | const ARROW = 'Arrow'; 2 | export const ARROW_LEFT = `${ ARROW }Left`; 3 | export const ARROW_RIGHT = `${ ARROW }Right`; 4 | export const ARROW_UP = `${ ARROW }Up`; 5 | export const ARROW_DOWN = `${ ARROW }Down`; -------------------------------------------------------------------------------- /src/js/constants/attributes.ts: -------------------------------------------------------------------------------- 1 | export const ROLE = 'role'; 2 | export const TAB_INDEX = 'tabindex'; 3 | export const DISABLED = 'disabled'; 4 | 5 | export const ARIA_PREFIX = 'aria-'; 6 | export const ARIA_CONTROLS = `${ ARIA_PREFIX }controls`; 7 | export const ARIA_CURRENT = `${ ARIA_PREFIX }current`; 8 | export const ARIA_SELECTED = `${ ARIA_PREFIX }selected`; 9 | export const ARIA_LABEL = `${ ARIA_PREFIX }label`; 10 | export const ARIA_LABELLEDBY = `${ ARIA_PREFIX }labelledby`; 11 | export const ARIA_HIDDEN = `${ ARIA_PREFIX }hidden`; 12 | export const ARIA_ORIENTATION = `${ ARIA_PREFIX }orientation`; 13 | export const ARIA_ROLEDESCRIPTION = `${ ARIA_PREFIX }roledescription`; 14 | export const ARIA_LIVE = `${ ARIA_PREFIX }live`; 15 | export const ARIA_BUSY = `${ ARIA_PREFIX }busy`; 16 | export const ARIA_ATOMIC = `${ ARIA_PREFIX }atomic`; 17 | 18 | /** 19 | * The array with all attributes to remove later. 20 | * Need to manually remove attributes that are not in this. 21 | * Note that removing `aria-live` disables the live region until the page reload. 22 | * 23 | * @since 3.0.0 24 | */ 25 | export const ALL_ATTRIBUTES = [ 26 | ROLE, 27 | TAB_INDEX, 28 | DISABLED, 29 | ARIA_CONTROLS, 30 | ARIA_CURRENT, 31 | ARIA_LABEL, 32 | ARIA_LABELLEDBY, 33 | ARIA_HIDDEN, 34 | ARIA_ORIENTATION, 35 | ARIA_ROLEDESCRIPTION, 36 | ]; 37 | -------------------------------------------------------------------------------- /src/js/constants/defaults.ts: -------------------------------------------------------------------------------- 1 | import { Options } from '../types'; 2 | import { CLASSES } from './classes'; 3 | import { I18N } from './i18n'; 4 | 5 | 6 | /** 7 | * The collection of default options. 8 | * Note that this collection does not contain all options. 9 | * 10 | * @since 3.0.0 11 | */ 12 | export const DEFAULTS: Options = { 13 | type : 'slide', 14 | role : 'region', 15 | speed : 400, 16 | perPage : 1, 17 | cloneStatus : true, 18 | arrows : true, 19 | pagination : true, 20 | paginationKeyboard: true, 21 | interval : 5000, 22 | pauseOnHover : true, 23 | pauseOnFocus : true, 24 | resetProgress : true, 25 | easing : 'cubic-bezier(0.25, 1, 0.5, 1)', 26 | drag : true, 27 | direction : 'ltr', 28 | trimSpace : true, 29 | focusableNodes : 'a, button, textarea, input, select, iframe', 30 | live : true, 31 | classes : CLASSES, 32 | i18n : I18N, 33 | reducedMotion: { 34 | speed : 0, 35 | rewindSpeed: 0, 36 | autoplay : 'pause', 37 | }, 38 | }; -------------------------------------------------------------------------------- /src/js/constants/directions.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Enumerates slides from left to right. 3 | */ 4 | export const LTR = 'ltr'; 5 | 6 | /** 7 | * Enumerates slides from right to left. 8 | */ 9 | export const RTL = 'rtl'; 10 | 11 | /** 12 | * Enumerates slides in a col. 13 | */ 14 | export const TTB = 'ttb'; 15 | -------------------------------------------------------------------------------- /src/js/constants/events.ts: -------------------------------------------------------------------------------- 1 | export const EVENT_MOUNTED = 'mounted'; 2 | export const EVENT_READY = 'ready'; 3 | export const EVENT_MOVE = 'move'; 4 | export const EVENT_MOVED = 'moved'; 5 | export const EVENT_CLICK = 'click'; 6 | export const EVENT_ACTIVE = 'active'; 7 | export const EVENT_INACTIVE = 'inactive'; 8 | export const EVENT_VISIBLE = 'visible'; 9 | export const EVENT_HIDDEN = 'hidden'; 10 | export const EVENT_REFRESH = 'refresh'; 11 | export const EVENT_UPDATED = 'updated'; 12 | export const EVENT_RESIZE = 'resize'; 13 | export const EVENT_RESIZED = 'resized'; 14 | export const EVENT_DRAG = 'drag'; 15 | export const EVENT_DRAGGING = 'dragging'; 16 | export const EVENT_DRAGGED = 'dragged'; 17 | export const EVENT_SCROLL = 'scroll'; 18 | export const EVENT_SCROLLED = 'scrolled'; 19 | export const EVENT_OVERFLOW = 'overflow'; 20 | export const EVENT_DESTROY = 'destroy'; 21 | export const EVENT_ARROWS_MOUNTED = 'arrows:mounted'; 22 | export const EVENT_ARROWS_UPDATED = 'arrows:updated'; 23 | export const EVENT_PAGINATION_MOUNTED = 'pagination:mounted'; 24 | export const EVENT_PAGINATION_UPDATED = 'pagination:updated'; 25 | export const EVENT_NAVIGATION_MOUNTED = 'navigation:mounted'; 26 | export const EVENT_AUTOPLAY_PLAY = 'autoplay:play'; 27 | export const EVENT_AUTOPLAY_PLAYING = 'autoplay:playing'; 28 | export const EVENT_AUTOPLAY_PAUSE = 'autoplay:pause'; 29 | export const EVENT_LAZYLOAD_LOADED = 'lazyload:loaded'; 30 | 31 | /** @internal */ 32 | export const EVENT_SLIDE_KEYDOWN = 'sk'; 33 | export const EVENT_SHIFTED = 'sh'; 34 | export const EVENT_END_INDEX_CHANGED = 'ei'; 35 | -------------------------------------------------------------------------------- /src/js/constants/i18n.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The collection of i18n strings. 3 | * 4 | * @since 3.0.0 5 | */ 6 | export const I18N = { 7 | prev : 'Previous slide', 8 | next : 'Next slide', 9 | first : 'Go to first slide', 10 | last : 'Go to last slide', 11 | slideX : 'Go to slide %s', 12 | pageX : 'Go to page %s', 13 | play : 'Start autoplay', 14 | pause : 'Pause autoplay', 15 | carousel : 'carousel', 16 | slide : 'slide', 17 | select : 'Select a slide to show', 18 | slideLabel: '%s of %s', // [ slide number ] / [ slide size ] 19 | }; 20 | -------------------------------------------------------------------------------- /src/js/constants/listener-options.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * AddEventListenerOptions for listeners that may prevent the browser scroll. 3 | * 4 | * @since 3.4.1 5 | */ 6 | export const SCROLL_LISTENER_OPTIONS = { passive: false, capture: true }; 7 | -------------------------------------------------------------------------------- /src/js/constants/media.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Matches when users request reducing non-essential animations. 3 | * 4 | * @link https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion 5 | */ 6 | export const MEDIA_PREFERS_REDUCED_MOTION = '(prefers-reduced-motion: reduce)'; -------------------------------------------------------------------------------- /src/js/constants/project.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The project code. 3 | * 4 | * @since 3.0.0 5 | */ 6 | export const PROJECT_CODE = 'splide'; 7 | 8 | /** 9 | * The data attribute prefix. 10 | * 11 | * @since 3.0.0 12 | */ 13 | export const DATA_ATTRIBUTE = `data-${ PROJECT_CODE }`; 14 | -------------------------------------------------------------------------------- /src/js/constants/states.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Splide has been just created. 3 | */ 4 | export const CREATED = 1; 5 | 6 | /** 7 | * Splide has mounted components. 8 | */ 9 | export const MOUNTED = 2; 10 | 11 | /** 12 | * Splide is ready. 13 | */ 14 | export const IDLE = 3; 15 | 16 | /** 17 | * Splide is moving. 18 | */ 19 | export const MOVING = 4; 20 | 21 | /** 22 | * Splide is moving. 23 | */ 24 | export const SCROLLING = 5; 25 | 26 | /** 27 | * The user is dragging the slider. 28 | */ 29 | export const DRAGGING = 6; 30 | 31 | /** 32 | * Splide has been destroyed. 33 | */ 34 | export const DESTROYED = 7; 35 | 36 | /** 37 | * The collection of all states. 38 | * 39 | * @since 3.0.0 40 | */ 41 | export const STATES = { 42 | CREATED, 43 | MOUNTED, 44 | IDLE, 45 | MOVING, 46 | SCROLLING, 47 | DRAGGING, 48 | DESTROYED, 49 | }; 50 | -------------------------------------------------------------------------------- /src/js/constants/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The type for the regular slider. 3 | * 4 | * @since 3.0.0 5 | */ 6 | export const SLIDE = 'slide'; 7 | 8 | /** 9 | * The type for the carousel slider. 10 | * 11 | * @since 3.0.0 12 | */ 13 | export const LOOP = 'loop'; 14 | 15 | /** 16 | * The type for the fade slider that can not have multiple slides in a page. 17 | * 18 | * @since 3.0.0 19 | */ 20 | export const FADE = 'fade'; 21 | -------------------------------------------------------------------------------- /src/js/constructors/EventBinder/test/destroy.test.ts: -------------------------------------------------------------------------------- 1 | import { fire } from '../../../test'; 2 | import { EventBinder } from '../EventBinder'; 3 | 4 | 5 | describe( 'EventBinder#destroy()', () => { 6 | const div = document.createElement( 'div' ); 7 | 8 | test( 'can remove all listeners.', () => { 9 | const { bind, destroy } = EventBinder(); 10 | const callback1 = jest.fn(); 11 | const callback2 = jest.fn(); 12 | 13 | bind( window, 'resize', callback1 ); 14 | bind( div, 'click', callback2 ); 15 | 16 | destroy(); 17 | 18 | fire( window, 'resize' ); 19 | fire( div, 'click' ); 20 | 21 | expect( callback1 ).not.toHaveBeenCalled(); 22 | expect( callback2 ).not.toHaveBeenCalled(); 23 | } ); 24 | 25 | test( 'should not remove listeners bound by other binders.', () => { 26 | const binder1 = EventBinder(); 27 | const binder2 = EventBinder(); 28 | const binder3 = EventBinder(); 29 | 30 | const binder1Callback = jest.fn(); 31 | const binder2Callback = jest.fn(); 32 | const binder3Callback = jest.fn(); 33 | 34 | binder1.bind( div, 'click', binder1Callback ); 35 | binder2.bind( div, 'click', binder2Callback ); 36 | binder3.bind( div, 'click', binder3Callback ); 37 | 38 | binder2.destroy(); 39 | 40 | fire( div, 'click' ); 41 | 42 | expect( binder1Callback ).toHaveBeenCalled(); 43 | expect( binder2Callback ).not.toHaveBeenCalled(); 44 | expect( binder3Callback ).toHaveBeenCalled(); 45 | } ); 46 | } ); 47 | -------------------------------------------------------------------------------- /src/js/constructors/EventBinder/test/dispatch.test.ts: -------------------------------------------------------------------------------- 1 | import { EventBinder } from '../EventBinder'; 2 | 3 | 4 | describe( 'EventBinder#dispatch()', () => { 5 | const div = document.createElement( 'div' ); 6 | 7 | test( 'can dispatch a custom event.', () => { 8 | const { dispatch } = EventBinder(); 9 | const callback = jest.fn(); 10 | 11 | div.addEventListener( 'myEvent', callback ); 12 | dispatch( div, 'myEvent' ); 13 | expect( callback ).toHaveBeenCalled(); 14 | } ); 15 | 16 | test( 'can dispatch a custom event with a detail property.', done => { 17 | const { dispatch } = EventBinder(); 18 | const array = [ 1, 2 ]; 19 | 20 | div.addEventListener( 'myEvent', e => { 21 | if ( e instanceof CustomEvent ) { 22 | expect( e.detail.a ).toBe( 1 ); 23 | expect( e.detail.b ).toBe( 'b' ); 24 | expect( e.detail.c ).toBe( array ); // Reference 25 | done(); 26 | } else { 27 | fail(); 28 | } 29 | } ); 30 | 31 | dispatch( div, 'myEvent', { a: 1, b: 'b', c: array } ); 32 | } ); 33 | } ); 34 | -------------------------------------------------------------------------------- /src/js/constructors/State/State.ts: -------------------------------------------------------------------------------- 1 | import { includes, toArray } from '../../utils'; 2 | 3 | 4 | /** 5 | * The interface for the State object. 6 | * 7 | * @since 3.0.0 8 | */ 9 | export interface StateObject { 10 | set( state: number ): void; 11 | is( states: number | number[] ): boolean; 12 | } 13 | 14 | /** 15 | * The function providing a super simple state system. 16 | * 17 | * @param initialState - Specifies the initial state. 18 | */ 19 | export function State( initialState: number ): StateObject { 20 | /** 21 | * The current state. 22 | */ 23 | let state = initialState; 24 | 25 | /** 26 | * Sets a new state. 27 | * 28 | * @param value - A new state value. 29 | */ 30 | function set( value: number ): void { 31 | state = value; 32 | } 33 | 34 | /** 35 | * Checks if the current state matches the provided one. 36 | * 37 | * @param states - A state to check. 38 | * 39 | * @return `true` if the current state is the provided one. 40 | */ 41 | function is( states: number | number[] ): boolean { 42 | return includes( toArray( states ), state ); 43 | } 44 | 45 | return { set, is }; 46 | } 47 | -------------------------------------------------------------------------------- /src/js/constructors/Throttle/Throttle.ts: -------------------------------------------------------------------------------- 1 | import { AnyFunction } from '../../types'; 2 | import { RequestInterval } from '../RequestInterval/RequestInterval'; 3 | 4 | 5 | /** 6 | * The interface for the returning value of the RequestInterval. 7 | * 8 | * @since 3.0.0 9 | */ 10 | export interface ThrottleInstance extends Function { 11 | ( ...args: Parameters ): void; 12 | } 13 | 14 | /** 15 | * Returns the throttled function. 16 | * 17 | * @param func - A function to throttle. 18 | * @param duration - Optional. Throttle duration in milliseconds. 19 | * 20 | * @return A throttled function. 21 | */ 22 | export function Throttle( 23 | func: F, 24 | duration?: number 25 | ): ThrottleInstance { 26 | const interval = RequestInterval( duration || 0, func, null, 1 ); 27 | 28 | return () => { 29 | interval.isPaused() && interval.start(); 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /src/js/constructors/Throttle/test/Throttle.test.ts: -------------------------------------------------------------------------------- 1 | import { Throttle } from '../Throttle'; 2 | 3 | 4 | describe( 'Throttle', () => { 5 | test( 'can control how often the callback function should be executed by the specified duration.', done => { 6 | const callback = jest.fn(); 7 | const duration = 1000; 8 | const throttled = Throttle( callback, duration ); 9 | 10 | throttled(); 11 | throttled(); 12 | throttled(); 13 | throttled(); 14 | 15 | expect( callback ).toHaveBeenCalledTimes( 0 ); 16 | 17 | // In the half way of the interval. 18 | setTimeout( () => { 19 | throttled(); 20 | throttled(); 21 | throttled(); 22 | throttled(); 23 | 24 | expect( callback ).toHaveBeenCalledTimes( 0 ); 25 | }, duration / 2 ); 26 | 27 | // After the interval duration. 28 | setTimeout( () => { 29 | throttled(); 30 | throttled(); 31 | throttled(); 32 | throttled(); 33 | 34 | expect( callback ).toHaveBeenCalledTimes( 1 ); 35 | 36 | done(); 37 | }, duration + 100 ); 38 | } ); 39 | } ); 40 | -------------------------------------------------------------------------------- /src/js/constructors/index.ts: -------------------------------------------------------------------------------- 1 | export * from './EventBinder/EventBinder'; 2 | export * from './EventInterface/EventInterface'; 3 | export * from './RequestInterval/RequestInterval'; 4 | export * from './State/State'; 5 | export * from './Throttle/Throttle'; 6 | -------------------------------------------------------------------------------- /src/js/core/Splide/test/general.test.ts: -------------------------------------------------------------------------------- 1 | import { init } from '../../../test'; 2 | import { DEFAULTS } from '../../../constants/defaults'; 3 | 4 | 5 | describe( 'Splide', () => { 6 | test( 'can merge options to defaults.', () => { 7 | const splide = init( { width: 200, height: 200 } ); 8 | const { options } = splide; 9 | 10 | expect( options.width ).toBe( 200 ); 11 | expect( options.height ).toBe( 200 ); 12 | expect( options.type ).toBe( DEFAULTS.type ); 13 | expect( options.speed ).toBe( DEFAULTS.speed ); 14 | } ); 15 | 16 | test( 'can merge options provided by the data attribute.', () => { 17 | const data = { width: 100, height: 100, type: 'loop', waitForTransition: false }; 18 | const splide = init( { width: 200, height: 200 }, { json: JSON.stringify( data ) } ); 19 | const { options } = splide; 20 | 21 | expect( options.width ).toBe( 100 ); 22 | expect( options.height ).toBe( 100 ); 23 | expect( options.type ).toBe( 'loop' ); 24 | expect( options.waitForTransition ).toBe( false ); 25 | } ); 26 | } ); 27 | -------------------------------------------------------------------------------- /src/js/index.ts: -------------------------------------------------------------------------------- 1 | export { Splide } from './core/Splide/Splide'; 2 | export { Splide as default } from './core/Splide/Splide'; 3 | export { SplideRenderer } from './renderer/SplideRenderer/SplideRenderer'; 4 | export * from './components/types'; 5 | export * from './constructors'; 6 | export * from './types'; 7 | export * from './constants/events'; 8 | export * from './constants/classes'; 9 | export * from './constants/defaults'; 10 | export * from './constants/directions'; 11 | export * from './constants/types'; 12 | -------------------------------------------------------------------------------- /src/js/renderer/constants/classes.ts: -------------------------------------------------------------------------------- 1 | export const CLASS_RENDERED = 'is-rendered'; 2 | -------------------------------------------------------------------------------- /src/js/renderer/constants/defaults.ts: -------------------------------------------------------------------------------- 1 | import { RendererConfig } from '../types/types'; 2 | 3 | 4 | /** 5 | * Default options for generating static HTML. 6 | * 7 | * @since 3.0.0 8 | */ 9 | export const RENDERER_DEFAULT_CONFIG: RendererConfig = { 10 | listTag : 'ul', 11 | slideTag: 'li', 12 | }; 13 | -------------------------------------------------------------------------------- /src/js/renderer/types/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The interface for the content of each slide. 3 | * 4 | * @since 3.0.0 5 | */ 6 | export interface SlideContent { 7 | /** 8 | * The HTML or text for each slide. 9 | */ 10 | html?: string; 11 | 12 | /** 13 | * The collection of styles. They will remain after Splide is applied. 14 | */ 15 | styles?: Record; 16 | 17 | /** 18 | * The collection of attributes. They will remain after Splide is applied. 19 | */ 20 | attrs?: Record; 21 | } 22 | 23 | /** 24 | * The interface for the config of the renderer. 25 | * 26 | * @since 3.0.0 27 | */ 28 | export interface RendererConfig { 29 | /** 30 | * The slider ID. 31 | */ 32 | id?: string; 33 | 34 | /** 35 | * The additional class for the root element. 36 | */ 37 | rootClass?: string; 38 | 39 | /** 40 | * The tag used for the list element. 41 | */ 42 | listTag?: string; 43 | 44 | /** 45 | * The tag used for each slide. 46 | */ 47 | slideTag?: string; 48 | 49 | /** 50 | * Determines whether to render arrows or not. 51 | */ 52 | arrows?: boolean; 53 | 54 | /** 55 | * Keeps the slider hidden. 56 | */ 57 | hidden?: boolean; 58 | 59 | /** 60 | * Determines whether to wrap the track by the slider element or not. 61 | */ 62 | slider?: boolean; 63 | 64 | /** 65 | * The additional HTML rendered before the slider element. 66 | */ 67 | beforeSlider?: string; 68 | 69 | /** 70 | * The additional HTML rendered after the slider element. 71 | */ 72 | afterSlider?: string; 73 | 74 | /** 75 | * The additional HTML rendered before the track element. 76 | */ 77 | beforeTrack?: string; 78 | 79 | /** 80 | * The additional HTML rendered after the track element. 81 | */ 82 | afterTrack?: string; 83 | } 84 | -------------------------------------------------------------------------------- /src/js/test/assets/css/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | background : #555; 3 | /*height: 200vh;*/ 4 | font-family: Roboto, sans-serif; 5 | line-height: 1.5; 6 | padding: 2rem; 7 | } 8 | 9 | img { 10 | width: 100%; 11 | height: auto; 12 | } 13 | 14 | /*button:focus {*/ 15 | /* outline: 2px solid yellowgreen;*/ 16 | /*}*/ 17 | 18 | .splide { 19 | margin: 2rem auto; 20 | } 21 | 22 | .splide__track { 23 | } 24 | 25 | .splide__slide { 26 | border: 2px solid transparent; 27 | } 28 | 29 | .splide__slide.is-visible { 30 | border: 2px solid darkgray; 31 | } 32 | 33 | .splide__slide.is-active { 34 | border: 2px solid yellowgreen; 35 | } 36 | 37 | /*.splide__slide:focus, .splide__arrow:focus, .splide__pagination__page:focus {*/ 38 | /* border: 2px solid tomato !important;*/ 39 | /*}*/ 40 | 41 | .splide__pagination__page.is-active { 42 | background: deepskyblue; 43 | } 44 | 45 | .splide__arrow:disabled { 46 | background: darkorchid; 47 | } 48 | 49 | .splide__progress { 50 | margin: .5rem 0; 51 | } 52 | -------------------------------------------------------------------------------- /src/js/test/assets/images/pics/slide01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splidejs/splide/d7e1f08e6b4f4b02a7c6ccbfbeb2d569d85715e6/src/js/test/assets/images/pics/slide01.jpg -------------------------------------------------------------------------------- /src/js/test/assets/images/pics/slide02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splidejs/splide/d7e1f08e6b4f4b02a7c6ccbfbeb2d569d85715e6/src/js/test/assets/images/pics/slide02.jpg -------------------------------------------------------------------------------- /src/js/test/assets/images/pics/slide03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splidejs/splide/d7e1f08e6b4f4b02a7c6ccbfbeb2d569d85715e6/src/js/test/assets/images/pics/slide03.jpg -------------------------------------------------------------------------------- /src/js/test/assets/images/pics/slide04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splidejs/splide/d7e1f08e6b4f4b02a7c6ccbfbeb2d569d85715e6/src/js/test/assets/images/pics/slide04.jpg -------------------------------------------------------------------------------- /src/js/test/assets/images/pics/slide05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splidejs/splide/d7e1f08e6b4f4b02a7c6ccbfbeb2d569d85715e6/src/js/test/assets/images/pics/slide05.jpg -------------------------------------------------------------------------------- /src/js/test/assets/images/pics/slide06.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splidejs/splide/d7e1f08e6b4f4b02a7c6ccbfbeb2d569d85715e6/src/js/test/assets/images/pics/slide06.jpg -------------------------------------------------------------------------------- /src/js/test/assets/images/pics/slide07.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splidejs/splide/d7e1f08e6b4f4b02a7c6ccbfbeb2d569d85715e6/src/js/test/assets/images/pics/slide07.jpg -------------------------------------------------------------------------------- /src/js/test/assets/images/pics/slide08.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splidejs/splide/d7e1f08e6b4f4b02a7c6ccbfbeb2d569d85715e6/src/js/test/assets/images/pics/slide08.jpg -------------------------------------------------------------------------------- /src/js/test/assets/images/pics/slide09.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splidejs/splide/d7e1f08e6b4f4b02a7c6ccbfbeb2d569d85715e6/src/js/test/assets/images/pics/slide09.jpg -------------------------------------------------------------------------------- /src/js/test/assets/images/pics/slide10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splidejs/splide/d7e1f08e6b4f4b02a7c6ccbfbeb2d569d85715e6/src/js/test/assets/images/pics/slide10.jpg -------------------------------------------------------------------------------- /src/js/test/assets/images/pics/slide11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splidejs/splide/d7e1f08e6b4f4b02a7c6ccbfbeb2d569d85715e6/src/js/test/assets/images/pics/slide11.jpg -------------------------------------------------------------------------------- /src/js/test/assets/images/pics/slide12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splidejs/splide/d7e1f08e6b4f4b02a7c6ccbfbeb2d569d85715e6/src/js/test/assets/images/pics/slide12.jpg -------------------------------------------------------------------------------- /src/js/test/assets/images/pics/slide13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splidejs/splide/d7e1f08e6b4f4b02a7c6ccbfbeb2d569d85715e6/src/js/test/assets/images/pics/slide13.jpg -------------------------------------------------------------------------------- /src/js/test/assets/images/pics/slide14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splidejs/splide/d7e1f08e6b4f4b02a7c6ccbfbeb2d569d85715e6/src/js/test/assets/images/pics/slide14.jpg -------------------------------------------------------------------------------- /src/js/test/assets/images/pics/slide15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splidejs/splide/d7e1f08e6b4f4b02a7c6ccbfbeb2d569d85715e6/src/js/test/assets/images/pics/slide15.jpg -------------------------------------------------------------------------------- /src/js/test/assets/images/pics/slide16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splidejs/splide/d7e1f08e6b4f4b02a7c6ccbfbeb2d569d85715e6/src/js/test/assets/images/pics/slide16.jpg -------------------------------------------------------------------------------- /src/js/test/assets/images/pics/slide17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splidejs/splide/d7e1f08e6b4f4b02a7c6ccbfbeb2d569d85715e6/src/js/test/assets/images/pics/slide17.jpg -------------------------------------------------------------------------------- /src/js/test/assets/images/pics/slide18.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splidejs/splide/d7e1f08e6b4f4b02a7c6ccbfbeb2d569d85715e6/src/js/test/assets/images/pics/slide18.jpg -------------------------------------------------------------------------------- /src/js/test/assets/images/pics/slide19.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splidejs/splide/d7e1f08e6b4f4b02a7c6ccbfbeb2d569d85715e6/src/js/test/assets/images/pics/slide19.jpg -------------------------------------------------------------------------------- /src/js/test/assets/images/pics/slide20.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splidejs/splide/d7e1f08e6b4f4b02a7c6ccbfbeb2d569d85715e6/src/js/test/assets/images/pics/slide20.jpg -------------------------------------------------------------------------------- /src/js/test/assets/images/planets/mars.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splidejs/splide/d7e1f08e6b4f4b02a7c6ccbfbeb2d569d85715e6/src/js/test/assets/images/planets/mars.jpg -------------------------------------------------------------------------------- /src/js/test/assets/images/planets/neptune.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splidejs/splide/d7e1f08e6b4f4b02a7c6ccbfbeb2d569d85715e6/src/js/test/assets/images/planets/neptune.jpg -------------------------------------------------------------------------------- /src/js/test/assets/images/planets/saturn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splidejs/splide/d7e1f08e6b4f4b02a7c6ccbfbeb2d569d85715e6/src/js/test/assets/images/planets/saturn.jpg -------------------------------------------------------------------------------- /src/js/test/fixtures/constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The dummy url. 3 | */ 4 | export const URL = 'https://test.com'; 5 | 6 | /** 7 | * The root and track width. 8 | */ 9 | export const SLIDER_WIDTH = 1280; 10 | -------------------------------------------------------------------------------- /src/js/test/fixtures/index.ts: -------------------------------------------------------------------------------- 1 | export * from './html'; 2 | -------------------------------------------------------------------------------- /src/js/test/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | HTML 7 | 8 | 9 | 10 | 11 | 22 | 23 | 24 | 25 |
    26 |
    27 |
      28 |
    • 29 | 30 |
    • 31 |
    • 32 | 33 |
    • 34 |
    • 35 | 36 |
    • 37 |
    • 38 | 39 |
    • 40 |
    • 41 | 42 |
    • 43 |
    • 44 | 45 |
    • 46 |
    • 47 | 48 |
    • 49 |
    • 50 | 51 |
    • 52 |
    • 53 | 54 |
    • 55 |
    • 56 | 57 |
    • 58 |
    59 |
    60 |
    61 | 62 | 63 | -------------------------------------------------------------------------------- /src/js/test/index.ts: -------------------------------------------------------------------------------- 1 | export * from './fixtures'; 2 | export * from './utils'; 3 | -------------------------------------------------------------------------------- /src/js/test/jest/setup.ts: -------------------------------------------------------------------------------- 1 | window.matchMedia = () => ( { 2 | matches : false, // All queries match the media string. 3 | media : '', 4 | onchange : null, 5 | addListener : jest.fn(), 6 | removeListener : jest.fn(), 7 | addEventListener : jest.fn(), 8 | removeEventListener: jest.fn(), 9 | dispatchEvent : jest.fn(), 10 | } as MediaQueryList ); -------------------------------------------------------------------------------- /src/js/test/php/examples/add.php: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Add / Remove 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /src/js/test/php/examples/autoHeight.php: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Auto Height 12 | 13 | 14 | 15 | 16 | 17 | 32 | 33 | 38 | 39 | 40 | 41 |
    42 |
    43 |
      44 |
    • 45 | 46 |
    • 47 |
    • 48 | 49 |
    • 50 |
    • 51 | 52 |
    • 53 |
    • 54 | 55 |
    • 56 |
    • 57 | 58 |
    • 59 |
    • 60 | 61 |
    • 62 |
    63 |
    64 |
    65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /src/js/test/php/examples/body.php: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Body 14 | 15 | 16 | 17 | 18 | 19 | 34 | 35 | 36 | 37 |
    38 | 39 | aa 40 |
    41 | 42 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/js/test/php/examples/drag-free.php: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Drag Free 14 | 15 | 16 | 17 | 18 | 19 | 35 | 36 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/js/test/php/examples/extension.php: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Extension 14 | 15 | 16 | 17 | 18 | 19 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/js/test/php/examples/fixedSize.php: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Fixed Size 14 | 15 | 16 | 17 | 18 | 19 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/js/test/php/examples/json.php: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | JSON 14 | 15 | 16 | 17 | 18 | 19 | 25 | 26 | 27 | 28 | 'fade', 31 | // 'perPage' => 2, 32 | ]; 33 | ?> 34 | 35 |
    36 |
    37 |
    38 | 39 |
    40 |
    41 |
    42 | 43 |
    
    44 | 
    45 | 
    46 | 
    47 | 
    
    
    --------------------------------------------------------------------------------
    /src/js/test/php/examples/nest.php:
    --------------------------------------------------------------------------------
     1 | 
     7 | 
     8 | 
     9 | 
    10 | 
    11 |   
    12 |   
    13 |   Nest
    14 | 
    15 |   
    16 |   
    17 |   
    18 | 
    19 |   
    45 | 
    46 | 
    47 | 
    48 | 
    49 |
    50 |
      51 | 52 | 53 |
      54 | 55 |
      56 | 57 |
      58 | 59 |
      60 |
    61 |
    62 |
    63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/js/test/php/examples/rtl.php: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | RTL 14 | 15 | 16 | 17 | 18 | 19 | 35 | 36 | 38 | 39 | 40 | 41 | 42 | 43 |
    
    44 | 
    45 | 
    46 | 
    47 | 
    
    
    --------------------------------------------------------------------------------
    /src/js/test/php/examples/ttb.php:
    --------------------------------------------------------------------------------
     1 | 
     7 | 
     8 | 
     9 | 
    10 | 
    11 |   
    12 |   
    13 |   Vertical Slider
    14 | 
    15 |   
    16 |   
    17 |   
    18 | 
    19 |   
    40 | 
    41 | 
    42 | 
    43 | 
    44 | 
    45 | 
    46 | 
    47 | 
    
    
    --------------------------------------------------------------------------------
    /src/js/test/php/parts.php:
    --------------------------------------------------------------------------------
     1 | ', $id );
     4 |   echo '
    '; 5 | echo '
      '; 6 | 7 | render_slides( $number, $text ); 8 | 9 | echo '
    '; 10 | echo '
    '; 11 | echo ''; 12 | } 13 | 14 | function render_slides( $number = 10, $text = false ) { 15 | for ( $i = 0; $i < $number; $i++ ) { 16 | echo '
  • '; 17 | 18 | if ( $text ) { 19 | printf( '%s', $i + 1 ); 20 | } else { 21 | printf( '', $i + 1 ); 22 | } 23 | 24 | echo '
  • ' . PHP_EOL; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/js/test/php/settings.php: -------------------------------------------------------------------------------- 1 | 'default', 5 | ]; 6 | } 7 | -------------------------------------------------------------------------------- /src/js/test/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './utils'; 2 | -------------------------------------------------------------------------------- /src/js/transitions/Fade/Fade.ts: -------------------------------------------------------------------------------- 1 | import { EVENT_MOUNTED, EVENT_REFRESH } from '../../constants/events'; 2 | import { EventInterface } from '../../constructors'; 3 | import { Splide } from '../../core/Splide/Splide'; 4 | import { Components, Options, TransitionComponent } from '../../types'; 5 | import { nextTick, noop } from '../../utils'; 6 | 7 | 8 | /** 9 | * The component for the fade transition. 10 | * 11 | * @since 3.0.0 12 | * 13 | * @param Splide - A Splide instance. 14 | * @param Components - A collection of components. 15 | * @param options - Options. 16 | * 17 | * @return A Transition component object. 18 | */ 19 | export function Fade( Splide: Splide, Components: Components, options: Options ): TransitionComponent { 20 | const { Slides } = Components; 21 | 22 | /** 23 | * Called when the component is mounted. 24 | */ 25 | function mount(): void { 26 | EventInterface( Splide ).on( [ EVENT_MOUNTED, EVENT_REFRESH ], init ); 27 | } 28 | 29 | /** 30 | * Initializes the component. 31 | * Offsets all slides for stacking them onto the head of the list. 32 | * The `nextTick` disables the initial fade transition of the first slide. 33 | */ 34 | function init(): void { 35 | Slides.forEach( Slide => { 36 | Slide.style( 'transform', `translateX(-${ 100 * Slide.index }%)` ); 37 | } ); 38 | } 39 | 40 | /** 41 | * Starts the transition. 42 | * 43 | * @param index - A slide index to be active. 44 | * @param done - The callback function that must be called after the transition ends. 45 | */ 46 | function start( index: number, done: () => void ): void { 47 | Slides.style( 'transition', `opacity ${ options.speed }ms ${ options.easing }` ); 48 | nextTick( done ); 49 | } 50 | 51 | return { 52 | mount, 53 | start, 54 | cancel: noop, 55 | }; 56 | } 57 | -------------------------------------------------------------------------------- /src/js/transitions/index.ts: -------------------------------------------------------------------------------- 1 | export { Fade } from './Fade/Fade'; 2 | export { Slide } from './Slide/Slide'; 3 | -------------------------------------------------------------------------------- /src/js/types/components.ts: -------------------------------------------------------------------------------- 1 | import * as Types from '../components/types'; 2 | import { BaseComponent, TransitionComponent } from './general'; 3 | 4 | 5 | /** 6 | * The interface for all components. 7 | * 8 | * @since 3.0.0 9 | */ 10 | export interface Components { 11 | [ key: string ]: BaseComponent | undefined; 12 | Media: Types.MediaComponent; 13 | Direction: Types.DirectionComponent; 14 | Elements: Types.ElementsComponent; 15 | Slides: Types.SlidesComponent; 16 | Layout: Types.LayoutComponent; 17 | Clones: Types.ClonesComponent; 18 | Move: Types.MoveComponent; 19 | Controller: Types.ControllerComponent; 20 | Arrows: Types.ArrowsComponent; 21 | Autoplay: Types.AutoplayComponent; 22 | Cover: Types.CoverComponent; 23 | Scroll: Types.ScrollComponent; 24 | Drag: Types.DragComponent; 25 | Keyboard: Types.KeyboardComponent; 26 | LazyLoad: Types.LazyLoadComponent; 27 | Pagination: Types.PaginationComponent; 28 | Sync: Types.SyncComponent; 29 | Wheel: Types.WheelComponent; 30 | Live: Types.LiveComponent; 31 | Transition: TransitionComponent; 32 | } 33 | -------------------------------------------------------------------------------- /src/js/types/events.ts: -------------------------------------------------------------------------------- 1 | import { PaginationData, PaginationItem } from '../components/Pagination/Pagination'; 2 | import { SlideComponent } from '../components/Slides/Slide'; 3 | import { Splide } from '../core/Splide/Splide'; 4 | import { Options } from './options'; 5 | 6 | 7 | /** 8 | * The interface for all internal events. 9 | * 10 | * @since 3.0.0 11 | */ 12 | export interface EventMap { 13 | 'mounted': () => void; 14 | 'ready': () => void; 15 | 'click': ( Slide: SlideComponent, e: MouseEvent ) => void; 16 | 'move': ( index: number, prev: number, dest: number ) => void; 17 | 'moved': ( index: number, prev: number, dest: number ) => void; 18 | 'active': ( Slide: SlideComponent ) => void; 19 | 'inactive': ( Slide: SlideComponent ) => void; 20 | 'visible': ( Slide: SlideComponent ) => void; 21 | 'hidden': ( Slide: SlideComponent ) => void; 22 | 'refresh': () => void; 23 | 'updated': ( options: Options ) => void; 24 | 'resize': () => void; 25 | 'resized': () => void; 26 | 'drag': () => void; 27 | 'dragging': () => void; 28 | 'dragged': () => void; 29 | 'scroll': () => void; 30 | 'scrolled': () => void; 31 | 'overflow': ( overflow: boolean ) => void; 32 | 'destroy': () => void; 33 | 'arrows:mounted': ( prev: HTMLButtonElement, next: HTMLButtonElement ) => void; 34 | 'arrows:updated': ( prev: HTMLButtonElement, next: HTMLButtonElement, prevIndex: number, nextIndex: number ) => void; 35 | 'pagination:mounted': ( data: PaginationData, item: PaginationItem ) => void; 36 | 'pagination:updated': ( data: PaginationData, prev: PaginationItem, curr: PaginationItem ) => void; 37 | 'navigation:mounted': ( splides: Splide[] ) => void; 38 | 'autoplay:play': () => void; 39 | 'autoplay:playing': ( rate: number ) => void; 40 | 'autoplay:pause': () => void; 41 | 'lazyload:loaded': ( img: HTMLImageElement, Slide: SlideComponent ) => void; 42 | } 43 | -------------------------------------------------------------------------------- /src/js/types/general.ts: -------------------------------------------------------------------------------- 1 | import { Splide } from '../core/Splide/Splide'; 2 | import { Components } from './components'; 3 | import { Options } from './options'; 4 | 5 | 6 | /** 7 | * The type for any function. 8 | * 9 | * @since 3.0.0 10 | */ 11 | export type AnyFunction = ( ...args: any[] ) => any; 12 | 13 | /** 14 | * The type for a component. 15 | * 16 | * @since 3.0.0 17 | */ 18 | export type ComponentConstructor = ( Splide: Splide, Components: Components, options: Options ) => BaseComponent; 19 | 20 | /** 21 | * The interface for any component. 22 | * 23 | * @since 3.0.0 24 | */ 25 | export interface BaseComponent { 26 | setup?(): void; 27 | mount?(): void; 28 | destroy?( completely?: boolean ): void; 29 | } 30 | 31 | /** 32 | * The interface for the Transition component. 33 | * 34 | * @since 3.0.0 35 | */ 36 | export interface TransitionComponent extends BaseComponent { 37 | start( index: number, done: () => void ): void; 38 | cancel(): void; 39 | } 40 | 41 | /** 42 | * The interface for info of a splide instance to sync with. 43 | * 44 | * @since 3.2.8 45 | */ 46 | export interface SyncTarget { 47 | splide: Splide; 48 | isParent?: boolean; 49 | } 50 | -------------------------------------------------------------------------------- /src/js/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './components'; 2 | export * from './events'; 3 | export * from './general'; 4 | export * from './options'; 5 | export * from './utils'; 6 | -------------------------------------------------------------------------------- /src/js/types/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Casts T to U. 3 | * 4 | * @internal 5 | */ 6 | export type Cast = T extends U ? T : U; 7 | 8 | /** 9 | * Makes the T easy to read. 10 | */ 11 | export type Resolve = { 12 | [ K in keyof T ]: T[ K ]; 13 | } & unknown; 14 | 15 | /** 16 | * Pushes U to tuple T. 17 | * 18 | * @internal 19 | */ 20 | export type Push = [ ...T, U ]; 21 | 22 | /** 23 | * Returns the first type of the tuple. 24 | * 25 | * @internal 26 | */ 27 | export type Head = ( ( ...args: T ) => any ) extends ( arg: infer A, ...args: any[] ) => any 28 | ? A 29 | : never; 30 | 31 | /** 32 | * Removes the first type from the tuple T. 33 | * 34 | * @internal 35 | */ 36 | export type Shift = ( ( ...args: T ) => any ) extends ( arg: any, ...args: infer A ) => any 37 | ? A 38 | : never; 39 | 40 | /** 41 | * Removes the N types from the tuple T. 42 | * 43 | * @internal 44 | */ 45 | export type ShiftN = { 46 | 0: T, 47 | 1: ShiftN, N, Push>, 48 | }[ C['length'] extends N ? 0 : 1 ] extends infer A ? Cast : never; -------------------------------------------------------------------------------- /src/js/utils/array/empty/empty.test.ts: -------------------------------------------------------------------------------- 1 | import { empty } from './empty'; 2 | 3 | 4 | describe( 'empty', () => { 5 | test( 'can empty an array.', () => { 6 | const array = [ 1, 2, 3 ]; 7 | empty( array ); 8 | 9 | expect( array[ 0 ] ).toBeUndefined(); 10 | expect( array.length ).toBe( 0 ); 11 | } ); 12 | } ); 13 | -------------------------------------------------------------------------------- /src/js/utils/array/empty/empty.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Empties the array. 3 | * 4 | * @param array - A array to empty. 5 | */ 6 | export function empty( array: any[] ): void { 7 | array.length = 0; 8 | } 9 | -------------------------------------------------------------------------------- /src/js/utils/array/forEach/forEach.test.ts: -------------------------------------------------------------------------------- 1 | import { forEach } from './forEach'; 2 | 3 | 4 | describe( 'forEach', () => { 5 | test( 'can iterate over an array.', () => { 6 | const array = [ 1, 2, 3 ]; 7 | const callback = jest.fn(); 8 | 9 | forEach( array, ( value, index, current ) => { 10 | expect( value ).toBe( array[ index ] ); 11 | expect( current ).toBe( array ); 12 | 13 | callback(); 14 | } ); 15 | 16 | expect( callback ).toHaveBeenCalledTimes( array.length ); 17 | } ); 18 | 19 | test( 'can push the provided value to a new array and iterate over it.', () => { 20 | const callback = jest.fn(); 21 | 22 | forEach( 1, ( value, index, current ) => { 23 | expect( value ).toBe( 1 ); 24 | expect( current ).toEqual( [ 1 ] ); 25 | 26 | callback(); 27 | } ); 28 | 29 | expect( callback ).toHaveBeenCalledTimes( 1 ); 30 | } ); 31 | } ); 32 | -------------------------------------------------------------------------------- /src/js/utils/array/forEach/forEach.ts: -------------------------------------------------------------------------------- 1 | import { toArray } from '../toArray/toArray'; 2 | 3 | 4 | /** 5 | * The extended `Array#forEach` method that accepts a single value as an argument. 6 | * 7 | * @param values - A value or values to iterate over. 8 | * @param iteratee - An iteratee function. 9 | */ 10 | export function forEach( values: T | T[], iteratee: ( value: T, index: number, array: T[] ) => void ): void { 11 | toArray( values ).forEach( iteratee ); 12 | } 13 | -------------------------------------------------------------------------------- /src/js/utils/array/includes/includes.test.ts: -------------------------------------------------------------------------------- 1 | import { includes } from './includes'; 2 | 3 | 4 | describe( 'includes', () => { 5 | const array = [ 1, 2, 3 ]; 6 | 7 | test( 'can check if the array includes a certain value or not.', () => { 8 | expect( includes( array, 1 ) ).toBe( true ); 9 | expect( includes( array, 2 ) ).toBe( true ); 10 | expect( includes( array, 3 ) ).toBe( true ); 11 | 12 | expect( includes( array, 5 ) ).toBe( false ); 13 | expect( includes( array, 'a' as any ) ).toBe( false ); 14 | expect( includes( array, true as any ) ).toBe( false ); 15 | } ); 16 | } ); 17 | -------------------------------------------------------------------------------- /src/js/utils/array/includes/includes.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if the array includes the value or not. 3 | * `Array#includes` is not supported by IE. 4 | * 5 | * @param array - An array. 6 | * @param value - A value to search for. 7 | * 8 | * @return `true` if the array includes the value, or otherwise `false`. 9 | */ 10 | export function includes( array: T[], value: T ): boolean { 11 | return array.indexOf( value ) > -1; 12 | } 13 | -------------------------------------------------------------------------------- /src/js/utils/array/index.ts: -------------------------------------------------------------------------------- 1 | export { empty } from './empty/empty'; 2 | export { forEach } from './forEach/forEach'; 3 | export { includes } from './includes/includes'; 4 | export { push } from './push/push'; 5 | export { toArray } from './toArray/toArray'; -------------------------------------------------------------------------------- /src/js/utils/array/push/push.test.ts: -------------------------------------------------------------------------------- 1 | import { push } from './push'; 2 | 3 | 4 | describe( 'push', () => { 5 | test( 'can push an item to an array.', () => { 6 | expect( push( [], 1 ) ).toEqual( [ 1 ] ); 7 | expect( push( [ 1 ], 2 ) ).toEqual( [ 1, 2 ] ); 8 | } ); 9 | 10 | test( 'can push items to an array.', () => { 11 | expect( push( [], [ 1, 2, 3 ] ) ).toEqual( [ 1, 2, 3 ] ); 12 | expect( push( [ 1, 2, 3 ], [ 4, 5, 6 ] ) ).toEqual( [ 1, 2, 3, 4, 5, 6 ] ); 13 | } ); 14 | 15 | test( 'should return the provided array itself.', () => { 16 | const array: number[] = []; 17 | expect( push( array, 1 ) ).toBe( array ); 18 | } ); 19 | } ); 20 | -------------------------------------------------------------------------------- /src/js/utils/array/push/push.ts: -------------------------------------------------------------------------------- 1 | import { toArray } from '../toArray/toArray'; 2 | 3 | 4 | /** 5 | * Extended `Array#push()` that accepts an item or an array with items. 6 | * 7 | * @param array - An array to push items. 8 | * @param items - An item or items to push. 9 | * 10 | * @return A provided array itself. 11 | */ 12 | export function push( array: T[], items: T | T[] ): T[] { 13 | array.push( ...toArray( items ) ); 14 | return array; 15 | } 16 | -------------------------------------------------------------------------------- /src/js/utils/array/toArray/toArray.test.ts: -------------------------------------------------------------------------------- 1 | import { toArray } from './toArray'; 2 | 3 | 4 | describe( 'toArray', () => { 5 | test( 'can push a provided value into an array.', () => { 6 | expect( toArray( 1 ) ).toEqual( [ 1 ] ); 7 | expect( toArray( true ) ).toEqual( [ true ] ); 8 | expect( toArray( { a: 1 } ) ).toStrictEqual( [ { a: 1 } ] ); 9 | } ); 10 | 11 | test( 'should return a provided value itself if it is already an array.', () => { 12 | const array = [ 1 ]; 13 | expect( toArray( array ) ).toBe( array ); 14 | } ); 15 | } ); 16 | -------------------------------------------------------------------------------- /src/js/utils/array/toArray/toArray.ts: -------------------------------------------------------------------------------- 1 | import { isArray } from '../../type/type'; 2 | 3 | 4 | /** 5 | * Push the provided value to an array if the value is not an array. 6 | * 7 | * @param value - A value to push. 8 | * 9 | * @return An array containing the value, or the value itself if it is already an array. 10 | */ 11 | export function toArray( value: T | T[] ): T[] { 12 | return isArray( value ) ? value : [ value ]; 13 | } 14 | -------------------------------------------------------------------------------- /src/js/utils/arrayLike/find/find.test.ts: -------------------------------------------------------------------------------- 1 | import { find } from './find'; 2 | 3 | 4 | describe( 'find', () => { 5 | test( 'can find a value in an array-like object that satisfies the predicate function.', () => { 6 | const arrayLike = { length: 3, 0: '1', 1: '2', 2: '3' }; 7 | 8 | expect( find( arrayLike, value => value === '2' ) ).toBe( '2' ); 9 | expect( find( arrayLike, ( value, index ) => index > 1 ) ).toBe( '3' ); 10 | } ); 11 | 12 | test( 'can find a value in an array that satisfies the predicate function.', () => { 13 | const array = [ 1, 2, 3 ]; 14 | 15 | expect( find( array, value => value === 2 ) ).toBe( 2 ); 16 | expect( find( array, ( value, index ) => index > 1 ) ).toBe( 3 ); 17 | } ); 18 | } ); 19 | -------------------------------------------------------------------------------- /src/js/utils/arrayLike/find/find.ts: -------------------------------------------------------------------------------- 1 | import { slice } from '../slice/slice'; 2 | 3 | 4 | /** 5 | * The find method for an array or array-like object, works in IE. 6 | * This method is not performant for a huge array. 7 | * 8 | * @param arrayLike - An array-like object. 9 | * @param predicate - The predicate function to test each element in the object. 10 | * 11 | * @return A found value if available, or otherwise `undefined`. 12 | */ 13 | export function find( 14 | arrayLike: ArrayLike, 15 | predicate: ( value: T, index: number, array: T[] ) => any 16 | ): T | undefined { 17 | return slice( arrayLike ).filter( predicate )[ 0 ]; 18 | } 19 | -------------------------------------------------------------------------------- /src/js/utils/arrayLike/index.ts: -------------------------------------------------------------------------------- 1 | export { slice } from './slice/slice'; 2 | export { find } from './find/find'; 3 | -------------------------------------------------------------------------------- /src/js/utils/arrayLike/slice/slice.test.ts: -------------------------------------------------------------------------------- 1 | import { slice } from './slice'; 2 | 3 | 4 | describe( 'slice', () => { 5 | const arrayLike = { length: 3, 0: '1', 1: '2', 2: '3' }; 6 | 7 | test( 'can slice an array-like object.', () => { 8 | expect( slice( arrayLike ) ).toEqual( [ '1', '2', '3' ] ); 9 | expect( slice( arrayLike, 1 ) ).toEqual( [ '2', '3' ] ); 10 | expect( slice( arrayLike, 1, 2 ) ).toEqual( [ '2' ] ); 11 | } ); 12 | 13 | test( 'can slice a node list.', () => { 14 | const div = document.createElement( 'div' ); 15 | div.innerHTML = `1234`; 16 | 17 | const spans = slice( div.children ); 18 | 19 | expect( spans.length ).toBe( 4 ); 20 | expect( spans[ 0 ] instanceof HTMLSpanElement ).toBe( true ); 21 | expect( spans[ 3 ].textContent ).toBe( '4' ); 22 | } ); 23 | } ); 24 | -------------------------------------------------------------------------------- /src/js/utils/arrayLike/slice/slice.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The slice method for an array-like object. 3 | * 4 | * @param arrayLike - An array-like object. 5 | * @param start - Optional. A start index. 6 | * @param end - Optional. A end index. 7 | * 8 | * @return An array with sliced elements. 9 | */ 10 | export function slice( arrayLike: ArrayLike, start?: number, end?: number ): T[] { 11 | return Array.prototype.slice.call( arrayLike, start, end ); 12 | } 13 | -------------------------------------------------------------------------------- /src/js/utils/dom/addClass/addClass.test.ts: -------------------------------------------------------------------------------- 1 | import { addClass } from './addClass'; 2 | 3 | 4 | describe( 'addClass', () => { 5 | beforeEach( () => { 6 | document.body.innerHTML = '
    '; 7 | } ); 8 | 9 | test( 'can add a class to the element.', () => { 10 | const container = document.getElementById( 'container' ); 11 | addClass( container, 'active' ); 12 | expect( container.classList.contains( 'active' ) ).toBe( true ); 13 | } ); 14 | 15 | test( 'can add classes to the element.', () => { 16 | const container = document.getElementById( 'container' ); 17 | 18 | addClass( container, [ 'active', 'visible' ] ); 19 | 20 | expect( container.classList.contains( 'active' ) ).toBe( true ); 21 | expect( container.classList.contains( 'visible' ) ).toBe( true ); 22 | } ); 23 | } ); 24 | -------------------------------------------------------------------------------- /src/js/utils/dom/addClass/addClass.ts: -------------------------------------------------------------------------------- 1 | import { isString } from '../../type/type'; 2 | import { toggleClass } from '../toggleClass/toggleClass'; 3 | 4 | 5 | /** 6 | * Adds classes to the element. 7 | * 8 | * @param elm - An element to add classes to. 9 | * @param classes - Classes to add. 10 | */ 11 | export function addClass( elm: Element, classes: string | string[] ): void { 12 | toggleClass( elm, isString( classes ) ? classes.split( ' ' ) : classes, true ); 13 | } 14 | -------------------------------------------------------------------------------- /src/js/utils/dom/append/append.test.ts: -------------------------------------------------------------------------------- 1 | import { append } from './append'; 2 | 3 | 4 | describe( 'append', () => { 5 | test( 'can append a child element to a parent element.', () => { 6 | const div = document.createElement( 'div' ); 7 | const span = document.createElement( 'span' ); 8 | 9 | append( div, span ); 10 | expect( div.firstElementChild ).toBe( span ); 11 | } ); 12 | 13 | test( 'can append children to a parent element.', () => { 14 | const div = document.createElement( 'div' ); 15 | const span1 = document.createElement( 'span' ); 16 | const span2 = document.createElement( 'span' ); 17 | const span3 = document.createElement( 'span' ); 18 | 19 | append( div, [ span1, span2, span3 ] ); 20 | 21 | expect( div.children[ 0 ] ).toBe( span1 ); 22 | expect( div.children[ 1 ] ).toBe( span2 ); 23 | expect( div.children[ 2 ] ).toBe( span3 ); 24 | } ); 25 | } ); 26 | -------------------------------------------------------------------------------- /src/js/utils/dom/append/append.ts: -------------------------------------------------------------------------------- 1 | import { forEach } from '../../array'; 2 | 3 | 4 | /** 5 | * Appends children to the parent element. 6 | * 7 | * @param parent - A parent element. 8 | * @param children - A child or children to append to the parent. 9 | */ 10 | export function append( parent: Element, children: Node | Node[] ): void { 11 | forEach( children, parent.appendChild.bind( parent ) ); 12 | } 13 | -------------------------------------------------------------------------------- /src/js/utils/dom/before/before.test.ts: -------------------------------------------------------------------------------- 1 | import { before } from './before'; 2 | 3 | 4 | describe( 'before', () => { 5 | beforeEach( () => { 6 | document.body.textContent = ''; 7 | } ); 8 | 9 | test( 'can insert a node before the reference node.', () => { 10 | const ref = document.createElement( 'a' ); 11 | const span1 = document.createElement( 'span' ); 12 | const span2 = document.createElement( 'span' ); 13 | 14 | document.body.appendChild( ref ); 15 | 16 | before( span1, ref ); 17 | expect( document.body.firstChild ).toBe( span1 ); 18 | expect( span1.nextSibling ).toBe( ref ); 19 | 20 | before( span2, ref ); 21 | expect( document.body.firstChild ).toBe( span1 ); 22 | expect( span1.nextSibling ).toBe( span2 ); 23 | expect( span2.nextSibling ).toBe( ref ); 24 | } ); 25 | 26 | test( 'can insert nodes before the reference node.', () => { 27 | const ref = document.createElement( 'a' ); 28 | const span1 = document.createElement( 'span' ); 29 | const span2 = document.createElement( 'span' ); 30 | const span3 = document.createElement( 'span' ); 31 | 32 | document.body.appendChild( ref ); 33 | 34 | before( [ span1, span2, span3 ], ref ); 35 | 36 | expect( document.body.children[ 0 ] ).toBe( span1 ); 37 | expect( document.body.children[ 1 ] ).toBe( span2 ); 38 | expect( document.body.children[ 2 ] ).toBe( span3 ); 39 | expect( document.body.children[ 3 ] ).toBe( ref ); 40 | } ); 41 | } ); 42 | -------------------------------------------------------------------------------- /src/js/utils/dom/before/before.ts: -------------------------------------------------------------------------------- 1 | import { forEach } from '../../array'; 2 | 3 | 4 | /** 5 | * Inserts a node or nodes before the specified reference node. 6 | * 7 | * @param nodes - A node or nodes to insert. 8 | * @param ref - A reference node. 9 | */ 10 | export function before( nodes: Node | Node[], ref: Node | null ): void { 11 | forEach( nodes, node => { 12 | const parent = ( ref || node ).parentNode; 13 | 14 | if ( parent ) { 15 | parent.insertBefore( node, ref ); 16 | } 17 | } ); 18 | } 19 | -------------------------------------------------------------------------------- /src/js/utils/dom/child/child.test.ts: -------------------------------------------------------------------------------- 1 | import { child } from './child'; 2 | 3 | 4 | describe( 'child', () => { 5 | beforeEach( () => { 6 | document.body.innerHTML = ` 7 |
    8 | 9 | 10 | 11 | 12 | 13 | 14 |
    15 | `; 16 | } ); 17 | 18 | test( 'can return the child that matches the specified selector.', () => { 19 | const container = document.getElementById( 'container' ); 20 | const span1 = child( container, 'span' ); 21 | expect( span1.id ).toBe( 'span1' ); 22 | 23 | const span2 = child( container, '#span2' ); 24 | expect( span2.id ).toBe( 'span2' ); 25 | 26 | const active = child( container, '.active' ); 27 | expect( active.id ).toBe( 'span1' ); 28 | } ); 29 | 30 | test( 'can return the firstElementChild if the selector is omitted.', () => { 31 | const container = document.getElementById( 'container' ); 32 | const span1 = child( container ); 33 | expect( span1.id ).toBe( 'span1' ); 34 | } ); 35 | 36 | test( 'should rerun undefined if no element is found.', () => { 37 | const container = document.getElementById( 'container' ); 38 | const elm = child( container, 'nothing' ); 39 | expect( elm ).toBeUndefined(); 40 | } ); 41 | } ); 42 | -------------------------------------------------------------------------------- /src/js/utils/dom/child/child.ts: -------------------------------------------------------------------------------- 1 | import { children } from '../children/children'; 2 | 3 | 4 | /** 5 | * Returns a child element that matches the specified tag or class name. 6 | * 7 | * @param parent - A parent element. 8 | * @param selector - A selector to filter children. 9 | * 10 | * @return A matched child element if available, or otherwise `undefined`. 11 | */ 12 | export function child( parent: HTMLElement, selector?: string ): E | undefined { 13 | return selector ? children( parent, selector )[ 0 ] : parent.firstElementChild as E; 14 | } 15 | -------------------------------------------------------------------------------- /src/js/utils/dom/children/children.test.ts: -------------------------------------------------------------------------------- 1 | import { children } from './children'; 2 | 3 | 4 | describe( 'children', () => { 5 | beforeEach( () => { 6 | document.body.innerHTML = ` 7 |
    8 | 9 | 10 | 11 | 12 | 13 | 14 |
    15 | `; 16 | } ); 17 | 18 | test( 'can return children that have the specified tag name.', () => { 19 | const container = document.getElementById( 'container' ); 20 | const spans = children( container, 'span' ); 21 | 22 | expect( spans.length ).toBe( 3 ); 23 | expect( spans[ 0 ].id ).toBe( 'span1' ); 24 | expect( spans[ 1 ].id ).toBe( 'span2' ); 25 | expect( spans[ 2 ].id ).toBe( 'span3' ); 26 | } ); 27 | 28 | test( 'can return children that have the specified class name.', () => { 29 | const container = document.getElementById( 'container' ); 30 | const spans = children( container, '.active' ); 31 | 32 | expect( spans.length ).toBe( 2 ); 33 | expect( spans[ 0 ].id ).toBe( 'span1' ); 34 | expect( spans[ 1 ].id ).toBe( 'button1' ); 35 | } ); 36 | 37 | test( 'should rerun an empty array if no element is found.', () => { 38 | const container = document.getElementById( 'container' ); 39 | const elms = children( container, '.nothing' ); 40 | expect( elms.length ).toBe( 0 ); 41 | } ); 42 | } ); 43 | -------------------------------------------------------------------------------- /src/js/utils/dom/children/children.ts: -------------------------------------------------------------------------------- 1 | import { slice } from '../../arrayLike'; 2 | import { matches } from '../matches/matches'; 3 | 4 | 5 | /** 6 | * Finds children that has the specified tag or class name. 7 | * 8 | * @param parent - A parent element. 9 | * @param selector - Optional. A selector to filter children. 10 | * 11 | * @return An array with filtered children. 12 | */ 13 | export function children( parent: HTMLElement, selector?: string ): E[] { 14 | const children = parent ? slice( parent.children ) as E[] : []; 15 | return selector ? children.filter( child => matches( child, selector ) ) : children; 16 | } 17 | -------------------------------------------------------------------------------- /src/js/utils/dom/closest/closest.test.ts: -------------------------------------------------------------------------------- 1 | import { closest } from './closest'; 2 | 3 | 4 | describe.each( [ [ 'native' ], [ 'polyfill' ] ] )( 'closest (%s)', ( env ) => { 5 | if ( env === 'polyfill' ) { 6 | // Forces to disable the native method. 7 | Element.prototype.closest = null as any; 8 | } 9 | 10 | beforeEach( () => { 11 | document.body.innerHTML = ` 12 |
    13 |
    14 |
    15 | start 16 |
    17 |
    18 |
    19 | `; 20 | } ); 21 | 22 | test( 'can find the closest element.', () => { 23 | const from = document.getElementById( 'start' ); 24 | 25 | if ( from ) { 26 | expect( closest( from, '#inner' )?.id ).toBe( 'inner' ); 27 | expect( closest( from, '#outer' )?.id ).toBe( 'outer' ); 28 | expect( closest( from, 'div' )?.id ).toBe( 'inner' ); 29 | expect( closest( from, '.wrapper' )?.id ).toBe( 'outer' ); 30 | } else { 31 | fail(); 32 | } 33 | } ); 34 | 35 | test( 'should include the provided element itself.', () => { 36 | const from = document.getElementById( 'start' ); 37 | 38 | if ( from ) { 39 | expect( closest( from, 'span' )?.id ).toBe( 'start' ); 40 | } else { 41 | fail(); 42 | } 43 | } ); 44 | 45 | test( 'should return null if no element is found.', () => { 46 | const from = document.getElementById( 'start' ); 47 | 48 | if ( from ) { 49 | expect( closest( from, 'invalid' ) ).toBeNull(); 50 | } else { 51 | fail(); 52 | } 53 | } ); 54 | } ); 55 | -------------------------------------------------------------------------------- /src/js/utils/dom/closest/closest.ts: -------------------------------------------------------------------------------- 1 | import { isFunction } from '../../type/type'; 2 | import { matches } from '../matches/matches'; 3 | 4 | 5 | /** 6 | * Starts from the provided element, searches for the first element that matches the selector in ascendants. 7 | * 8 | * @param from - An element to search from. 9 | * @param selector - A selector. 10 | * 11 | * @return The found element if available, or `null`. 12 | */ 13 | export function closest( from: HTMLElement, selector: string ): HTMLElement | null { 14 | if ( isFunction( from.closest ) ) { 15 | return from.closest( selector ); 16 | } 17 | 18 | let elm: HTMLElement | null = from; 19 | 20 | while ( elm && elm.nodeType === 1 ) { 21 | if ( matches( elm, selector ) ) { 22 | break; 23 | } 24 | 25 | elm = elm.parentElement; 26 | } 27 | 28 | return elm; 29 | } -------------------------------------------------------------------------------- /src/js/utils/dom/create/create.test.ts: -------------------------------------------------------------------------------- 1 | import { create } from './create'; 2 | 3 | 4 | describe( 'create', () => { 5 | test( 'can create an element by a tag name.', () => { 6 | const div = create( 'div' ); 7 | const iframe = create( 'iframe' ); 8 | 9 | expect( div instanceof HTMLDivElement ).toBe( true ); 10 | expect( iframe instanceof HTMLIFrameElement ).toBe( true ); 11 | } ); 12 | 13 | test( 'can create an element with setting attributes.', () => { 14 | const iframe = create( 'iframe', { width: 100, height: 200 } ); 15 | 16 | expect( iframe.getAttribute( 'width' ) ).toBe( '100' ); 17 | expect( iframe.getAttribute( 'height' ) ).toBe( '200' ); 18 | } ); 19 | } ); 20 | -------------------------------------------------------------------------------- /src/js/utils/dom/create/create.ts: -------------------------------------------------------------------------------- 1 | import { isString } from '../../type/type'; 2 | import { addClass } from '../addClass/addClass'; 3 | import { append } from '../append/append'; 4 | import { setAttribute } from '../setAttribute/setAttribute'; 5 | 6 | 7 | export function create( 8 | tag: K, 9 | attrs?: Record | string, 10 | parent?: HTMLElement 11 | ): HTMLElementTagNameMap[ K ]; 12 | 13 | export function create( 14 | tag: string, 15 | attrs?: Record | string, 16 | parent?: HTMLElement 17 | ): HTMLElement; 18 | 19 | /** 20 | * Creates a HTML element. 21 | * 22 | * @param tag - A tag name. 23 | * @param attrs - Optional. An object with attributes to apply the created element to, or a string with classes. 24 | * @param parent - Optional. A parent element where the created element is appended. 25 | */ 26 | export function create( 27 | tag: K, 28 | attrs?: Record | string, 29 | parent?: HTMLElement 30 | ): HTMLElementTagNameMap[ K ] { 31 | const elm = document.createElement( tag ); 32 | 33 | if ( attrs ) { 34 | isString( attrs ) ? addClass( elm, attrs ) : setAttribute( elm, attrs ); 35 | } 36 | 37 | parent && append( parent, elm ); 38 | 39 | return elm; 40 | } 41 | -------------------------------------------------------------------------------- /src/js/utils/dom/display/display.test.ts: -------------------------------------------------------------------------------- 1 | import { display } from './display'; 2 | 3 | 4 | describe( 'display', () => { 5 | test( 'can set a new display value.', () => { 6 | const div = document.createElement( 'div' ); 7 | 8 | display( div, 'none' ); 9 | expect( div.style.display ).toBe( 'none' ); 10 | 11 | display( div, 'flex' ); 12 | expect( div.style.display ).toBe( 'flex' ); 13 | 14 | display( div, '' ); 15 | expect( div.style.display ).toBe( '' ); 16 | } ); 17 | } ); 18 | -------------------------------------------------------------------------------- /src/js/utils/dom/display/display.ts: -------------------------------------------------------------------------------- 1 | import { style } from '../style/style'; 2 | 3 | 4 | /** 5 | * Sets the `display` CSS value to the element. 6 | * 7 | * @param elm - An element to set a new value to. 8 | * @param display - A new `display` value. 9 | */ 10 | export function display( elm: HTMLElement, display: string ): void { 11 | style( elm, 'display', display ); 12 | } 13 | -------------------------------------------------------------------------------- /src/js/utils/dom/focus/focus.test.ts: -------------------------------------------------------------------------------- 1 | import { focus } from './focus'; 2 | 3 | 4 | describe( 'focus', () => { 5 | test( 'can make an element focused if it is focusable.', () => { 6 | const div = document.createElement( 'div' ); 7 | 8 | div.tabIndex = 0; 9 | document.body.appendChild( div ); 10 | 11 | expect( document.activeElement ).not.toBe( div ); 12 | 13 | focus( div ); 14 | 15 | expect( document.activeElement ).toBe( div ); 16 | } ); 17 | } ); 18 | -------------------------------------------------------------------------------- /src/js/utils/dom/focus/focus.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Focuses the provided element without scrolling the ascendant element. 3 | * 4 | * @param elm - An element to focus. 5 | */ 6 | export function focus( elm: HTMLElement ): void { 7 | elm[ 'setActive' ] && elm[ 'setActive' ]() || elm.focus( { preventScroll: true } ); 8 | } 9 | -------------------------------------------------------------------------------- /src/js/utils/dom/getAttribute/getAttribute.test.ts: -------------------------------------------------------------------------------- 1 | import { getAttribute } from './getAttribute'; 2 | 3 | 4 | describe( 'getAttribute', () => { 5 | beforeEach( () => { 6 | document.body.innerHTML = '
    '; 7 | } ); 8 | 9 | test( 'can set an attribute to an element.', () => { 10 | const container = document.getElementById( 'container' ); 11 | 12 | container.setAttribute( 'aria-hidden', 'true' ); 13 | container.setAttribute( 'tabindex', '-1' ); 14 | 15 | expect( getAttribute( container, 'id' ) ).toBe( 'container' ); 16 | expect( getAttribute( container, 'aria-hidden' ) ).toBe( 'true' ); 17 | expect( getAttribute( container, 'tabindex' ) ).toBe( '-1' ); 18 | } ); 19 | } ); 20 | -------------------------------------------------------------------------------- /src/js/utils/dom/getAttribute/getAttribute.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the specified attribute value. 3 | * 4 | * @param elm - An element. 5 | * @param attr - An attribute to get. 6 | */ 7 | export function getAttribute( elm: Element, attr: string ): string | null { 8 | return elm.getAttribute( attr ); 9 | } 10 | -------------------------------------------------------------------------------- /src/js/utils/dom/hasClass/hasClass.test.ts: -------------------------------------------------------------------------------- 1 | import { hasClass } from './hasClass'; 2 | 3 | 4 | describe( 'hasClass', () => { 5 | test( 'can return true if the element contains the specified class.', () => { 6 | const container = document.createElement( 'div' ); 7 | container.classList.add( 'active' ); 8 | container.classList.add( 'visible' ); 9 | 10 | expect( hasClass( container, 'active' ) ).toBe( true ); 11 | expect( hasClass( container, 'visible' ) ).toBe( true ); 12 | } ); 13 | 14 | test( 'can return false if the element does not contain the specified class.', () => { 15 | const container = document.createElement( 'div' ); 16 | 17 | expect( hasClass( container, 'active' ) ).toBe( false ); 18 | expect( hasClass( container, 'visible' ) ).toBe( false ); 19 | } ); 20 | } ); 21 | -------------------------------------------------------------------------------- /src/js/utils/dom/hasClass/hasClass.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if the element contains the specified class or not. 3 | * 4 | * @param elm - An element to check. 5 | * @param className - A class name that may be contained by the element. 6 | * 7 | * @return `true` if the element contains the class, or otherwise `false`. 8 | */ 9 | export function hasClass( elm: Element, className: string ): boolean { 10 | return elm && elm.classList.contains( className ); 11 | } 12 | -------------------------------------------------------------------------------- /src/js/utils/dom/index.ts: -------------------------------------------------------------------------------- 1 | export { addClass } from './addClass/addClass'; 2 | export { append } from './append/append'; 3 | export { before } from './before/before'; 4 | export { child } from './child/child'; 5 | export { children } from './children/children'; 6 | export { create } from './create/create'; 7 | export { display } from './display/display'; 8 | export { focus } from './focus/focus'; 9 | export { getAttribute } from './getAttribute/getAttribute'; 10 | export { hasClass } from './hasClass/hasClass'; 11 | export { matches } from './matches/matches'; 12 | export { measure } from './measure/measure'; 13 | export { parseHtml } from './parseHtml/parseHtml'; 14 | export { prevent } from './prevent/prevent'; 15 | export { query } from './query/query'; 16 | export { queryAll } from './queryAll/queryAll'; 17 | export { rect } from './rect/rect'; 18 | export { remove } from './remove/remove'; 19 | export { removeAttribute } from './removeAttribute/removeAttribute'; 20 | export { removeClass } from './removeClass/removeClass'; 21 | export { setAttribute } from './setAttribute/setAttribute'; 22 | export { style } from './style/style'; 23 | export { timeOf } from './timeOf/timeOf'; 24 | export { toggleClass } from './toggleClass/toggleClass'; 25 | export { unit } from './unit/unit'; 26 | -------------------------------------------------------------------------------- /src/js/utils/dom/matches/matches.test.ts: -------------------------------------------------------------------------------- 1 | import { matches } from './matches'; 2 | 3 | 4 | describe( 'children', () => { 5 | beforeEach( () => { 6 | document.body.innerHTML = ` 7 |
    8 | 9 | 10 | 11 | 12 | 13 | 14 |
    15 | `; 16 | } ); 17 | 18 | test( 'can test if the selector matches the element or not.', () => { 19 | const container = document.getElementById( 'container' ); 20 | 21 | expect( matches( container, 'div' ) ).toBe( true ); 22 | expect( matches( container, '#container' ) ).toBe( true ); 23 | expect( matches( container, 'span' ) ).toBe( false ); 24 | 25 | const span = container.firstElementChild; 26 | 27 | expect( matches( span, 'span' ) ).toBe( true ); 28 | expect( matches( span, '#span1' ) ).toBe( true ); 29 | expect( matches( span, '.active' ) ).toBe( true ); 30 | expect( matches( span, '#container .active' ) ).toBe( true ); 31 | 32 | expect( matches( span, '#container' ) ).toBe( false ); 33 | expect( matches( span, '#span2' ) ).toBe( false ); 34 | } ); 35 | } ); 36 | -------------------------------------------------------------------------------- /src/js/utils/dom/matches/matches.ts: -------------------------------------------------------------------------------- 1 | import { isHTMLElement } from '../../type/type'; 2 | 3 | 4 | /** 5 | * Checks if the element can be selected by the provided selector or not. 6 | * 7 | * @param elm - An element to check. 8 | * @param selector - A selector to test. 9 | * 10 | * @return `true` if the selector matches the element, or otherwise `false`. 11 | */ 12 | export function matches( elm: Element | EventTarget, selector: string ): boolean { 13 | return isHTMLElement( elm ) && ( elm[ 'msMatchesSelector' ] || elm.matches ).call( elm, selector ); 14 | } 15 | -------------------------------------------------------------------------------- /src/js/utils/dom/measure/measure.ts: -------------------------------------------------------------------------------- 1 | import { isString } from '../../type/type'; 2 | import { create } from '../create/create'; 3 | import { rect } from '../rect/rect'; 4 | import { remove } from '../remove/remove'; 5 | 6 | 7 | /** 8 | * Attempts to convert the provided value to pixel as the relative value to the parent element. 9 | * 10 | * @param parent - A parent element. 11 | * @param value - A value to convert. 12 | * 13 | * @return A converted value in pixel. Unhandled values will become 0. 14 | */ 15 | export function measure( parent: HTMLElement, value: number | string ): number { 16 | if ( isString( value ) ) { 17 | const div = create( 'div', { style: `width: ${ value }; position: absolute;` }, parent ); 18 | value = rect( div ).width; 19 | remove( div ); 20 | } 21 | 22 | return value; 23 | } 24 | -------------------------------------------------------------------------------- /src/js/utils/dom/normalizeKey/normalizeKey.test.ts: -------------------------------------------------------------------------------- 1 | import { fire } from '../../../test'; 2 | import { NORMALIZATION_MAP, normalizeKey } from './normalizeKey'; 3 | 4 | 5 | describe( 'normalizeKey', () => { 6 | test( 'can normalize a key into a standard name.', () => { 7 | const keys = Object.keys( NORMALIZATION_MAP ); 8 | const callback = jest.fn(); 9 | 10 | keys.forEach( key => { 11 | expect( normalizeKey( key ) ).toBe( NORMALIZATION_MAP[ key ] ); 12 | callback(); 13 | } ); 14 | 15 | expect( callback ).toHaveBeenCalled(); 16 | } ); 17 | 18 | test( 'can return a normalized key from a Keyboard event object.', done => { 19 | window.addEventListener( 'keydown', e => { 20 | expect( normalizeKey( e ) ).toBe( 'ArrowUp' ); 21 | done(); 22 | } ); 23 | 24 | fire( window, 'keydown', { key: 'Up' } ); 25 | } ); 26 | 27 | test( 'should do the provided key as is if the normalization map does not include the passed key.', () => { 28 | expect( normalizeKey( 'a' ) ).toBe( 'a' ); 29 | expect( normalizeKey( 'F1' ) ).toBe( 'F1' ); 30 | } ); 31 | } ); -------------------------------------------------------------------------------- /src/js/utils/dom/normalizeKey/normalizeKey.ts: -------------------------------------------------------------------------------- 1 | import { ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT, ARROW_UP } from '../../../constants/arrows'; 2 | import { isString } from '../../type/type'; 3 | 4 | 5 | /** 6 | * The map to associate a non-standard name to the standard one. 7 | * 8 | * @since 4.0.0 9 | */ 10 | export const NORMALIZATION_MAP = { 11 | Spacebar: ' ', 12 | Right : ARROW_RIGHT, 13 | Left : ARROW_LEFT, 14 | Up : ARROW_UP, 15 | Down : ARROW_DOWN, 16 | }; 17 | 18 | /** 19 | * Normalizes the key. 20 | * 21 | * @param key - A string or a KeyboardEvent object. 22 | * 23 | * @return A normalized key. 24 | */ 25 | export function normalizeKey( key: string | KeyboardEvent ): string { 26 | key = isString( key ) ? key : key.key; 27 | return NORMALIZATION_MAP[ key ] || key; 28 | } -------------------------------------------------------------------------------- /src/js/utils/dom/parseHtml/parseHtml.test.ts: -------------------------------------------------------------------------------- 1 | import { parseHtml } from './parseHtml'; 2 | 3 | 4 | describe( 'parseHtml', () => { 5 | test( 'can parse the provided HTML string.', () => { 6 | const div = parseHtml( '
    content
    ' ); 7 | 8 | expect( div.id ).toBe( 'container' ); 9 | expect( div.classList.contains( 'active' ) ).toBe( true ); 10 | expect( div.firstElementChild.tagName.toUpperCase() ).toBe( 'SPAN' ); 11 | expect( div.firstElementChild.textContent ).toBe( 'content' ); 12 | } ); 13 | 14 | test( 'can parse the provided SVG string.', () => { 15 | const svg = parseHtml( '' ); 16 | 17 | expect( svg instanceof SVGElement ).toBe( true ); 18 | expect( svg.id ).toBe( 'icon' ); 19 | expect( svg.firstElementChild.tagName.toUpperCase() ).toBe( 'PATH' ); 20 | } ); 21 | } ); 22 | -------------------------------------------------------------------------------- /src/js/utils/dom/parseHtml/parseHtml.ts: -------------------------------------------------------------------------------- 1 | import { child } from '../child/child'; 2 | 3 | 4 | /** 5 | * Parses the provided HTML string and returns the first element. 6 | * 7 | * @param html - An HTML string to parse. 8 | * 9 | * @return An Element on success, or otherwise `undefined`. 10 | */ 11 | export function parseHtml( html: string ): E | undefined { 12 | return child( new DOMParser().parseFromString( html, 'text/html' ).body ); 13 | } 14 | -------------------------------------------------------------------------------- /src/js/utils/dom/prevent/prevent.test.ts: -------------------------------------------------------------------------------- 1 | import { fire } from '../../../test'; 2 | import { prevent } from './prevent'; 3 | 4 | 5 | describe( 'prevent', () => { 6 | test( 'can prevent the default browser action of an event.', done => { 7 | window.addEventListener( 'click', e => { 8 | prevent( e ); 9 | expect( e.defaultPrevented ).toBe( true ); 10 | done(); 11 | } ); 12 | 13 | fire( window, 'click', { timeStamp: 123 }, { cancelable: true } ); 14 | } ); 15 | } ); -------------------------------------------------------------------------------- /src/js/utils/dom/prevent/prevent.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Call the `preventDefault()` of the provided event. 3 | * 4 | * @param e - An Event object. 5 | * @param stopPropagation - Optional. Whether to stop the event propagation or not. 6 | */ 7 | export function prevent( e: Event, stopPropagation?: boolean ): void { 8 | e.preventDefault(); 9 | 10 | if ( stopPropagation ) { 11 | e.stopPropagation(); 12 | e.stopImmediatePropagation(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/js/utils/dom/query/query.test.ts: -------------------------------------------------------------------------------- 1 | import { query } from './query'; 2 | 3 | 4 | describe( 'query', () => { 5 | beforeEach( () => { 6 | document.body.innerHTML = ` 7 |
    8 | 1 9 |
    10 |
    11 | 2 12 |
    13 |
    14 | 3 15 |
    16 | `; 17 | } ); 18 | 19 | test( 'can return the first element that matches the specified selector.', () => { 20 | const div1 = query( document, 'div' ); 21 | expect( div1.id ).toBe( 'div1' ); 22 | 23 | const div3 = query( document, '#div3' ); 24 | expect( div3.id ).toBe( 'div3' ); 25 | } ); 26 | 27 | test( 'can accept a parent element to start find an element from.', () => { 28 | const div2 = query( document, '#div2' ); 29 | const span2 = query( div2, 'span' ); 30 | 31 | expect( span2.textContent ).toBe( '2' ); 32 | } ); 33 | 34 | test( 'should return `null` if nothing matches the selector.', () => { 35 | expect( query( document, '#nothing' ) ).toBeNull(); 36 | } ); 37 | } ); 38 | -------------------------------------------------------------------------------- /src/js/utils/dom/query/query.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns an element that matches the provided selector. 3 | * 4 | * @param parent - A parent element to start searching from. 5 | * @param selector - A selector to query. 6 | * 7 | * @return A found element or `null`. 8 | */ 9 | export function query( parent: Element | Document, selector: string ): E | null { 10 | return parent && parent.querySelector( selector ); 11 | } 12 | -------------------------------------------------------------------------------- /src/js/utils/dom/queryAll/queryAll.test.ts: -------------------------------------------------------------------------------- 1 | import { queryAll } from './queryAll'; 2 | 3 | 4 | describe( 'queryAll', () => { 5 | beforeEach( () => { 6 | document.body.innerHTML = ` 7 |
    8 | 1 9 |
    10 |
    11 | 2 12 |
    13 |
    14 | 3 15 |
    16 | `; 17 | } ); 18 | 19 | test( 'can get elements that match the selector.', () => { 20 | const divs = queryAll( document.body, 'div' ); 21 | expect( divs.length ).toBe( 3 ); 22 | expect( divs[ 0 ].id ).toBe( 'div1' ); 23 | expect( divs[ 1 ].id ).toBe( 'div2' ); 24 | expect( divs[ 2 ].id ).toBe( 'div3' ); 25 | 26 | const spans = queryAll( document.body, '#div1 span' ); 27 | 28 | expect( spans.length ).toBe( 1 ); 29 | expect( spans[ 0 ].textContent ).toBe( '1' ); 30 | } ); 31 | } ); 32 | -------------------------------------------------------------------------------- /src/js/utils/dom/queryAll/queryAll.ts: -------------------------------------------------------------------------------- 1 | import { slice } from '../../arrayLike'; 2 | 3 | 4 | /** 5 | * Returns elements that match the provided selector. 6 | * 7 | * @param parent - A parent element to start searching from. 8 | * @param selector - A selector to query. 9 | * 10 | * @return An array with matched elements. 11 | */ 12 | export function queryAll( parent: Element | Document, selector?: string ): E[] { 13 | return selector ? slice( parent.querySelectorAll( selector ) ) : []; 14 | } 15 | -------------------------------------------------------------------------------- /src/js/utils/dom/rect/rect.test.ts: -------------------------------------------------------------------------------- 1 | import { rect } from './rect'; 2 | 3 | 4 | describe( 'rect', () => { 5 | test( 'can return a DOMRect object.', () => { 6 | const div = document.createElement( 'div' ); 7 | 8 | expect( rect( div ).width ).toBe( 0 ); 9 | expect( rect( div ).left ).toBe( 0 ); 10 | } ); 11 | } ); 12 | -------------------------------------------------------------------------------- /src/js/utils/dom/rect/rect.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns a DOMRect object of the provided element. 3 | * 4 | * @param target - An element. 5 | */ 6 | export function rect( target: Element ): DOMRect { 7 | return target.getBoundingClientRect(); 8 | } 9 | -------------------------------------------------------------------------------- /src/js/utils/dom/remove/remove.test.ts: -------------------------------------------------------------------------------- 1 | import { remove } from './remove'; 2 | 3 | 4 | describe( 'remove', () => { 5 | test( 'can remove an element from its parent.', () => { 6 | const div = document.createElement( 'div' ); 7 | const span = document.createElement( 'span' ); 8 | 9 | div.appendChild( span ); 10 | expect( div.firstElementChild ).toBe( span ); 11 | 12 | remove( span ); 13 | expect( div.children.length ).toBe( 0 ); 14 | } ); 15 | 16 | test( 'can remove elements from its parent.', () => { 17 | const div = document.createElement( 'div' ); 18 | const span1 = document.createElement( 'span' ); 19 | const span2 = document.createElement( 'span' ); 20 | const span3 = document.createElement( 'span' ); 21 | 22 | div.appendChild( span1 ); 23 | div.appendChild( span2 ); 24 | div.appendChild( span3 ); 25 | expect( div.children[ 0 ] ).toBe( span1 ); 26 | expect( div.children[ 1 ] ).toBe( span2 ); 27 | expect( div.children[ 2 ] ).toBe( span3 ); 28 | 29 | remove( [ span1, span2, span3 ] ); 30 | expect( div.children.length ).toBe( 0 ); 31 | } ); 32 | 33 | test( 'can remove a text node from its parent.', () => { 34 | const span = document.createElement( 'span' ); 35 | const node = document.createTextNode( 'sample' ); 36 | 37 | span.appendChild( node ); 38 | expect( span.textContent ).toBe( 'sample' ); 39 | 40 | remove( node ); 41 | expect( span.textContent ).toBe( '' ); 42 | } ); 43 | } ); 44 | -------------------------------------------------------------------------------- /src/js/utils/dom/remove/remove.ts: -------------------------------------------------------------------------------- 1 | import { forEach } from '../../array'; 2 | 3 | 4 | /** 5 | * Removes the provided node from its parent. 6 | * 7 | * @param nodes - A node or nodes to remove. 8 | */ 9 | export function remove( nodes: Node | Node[] ): void { 10 | forEach( nodes, node => { 11 | if ( node && node.parentNode ) { 12 | node.parentNode.removeChild( node ); 13 | } 14 | } ); 15 | } 16 | -------------------------------------------------------------------------------- /src/js/utils/dom/removeAttribute/removeAttribute.test.ts: -------------------------------------------------------------------------------- 1 | import { removeAttribute } from './removeAttribute'; 2 | 3 | 4 | describe( 'removeAttribute', () => { 5 | test( 'can remove an attribute from an element.', () => { 6 | const div = document.createElement( 'div' ); 7 | 8 | div.setAttribute( 'aria-hidden', 'true' ); 9 | div.setAttribute( 'tabindex', '-1' ); 10 | 11 | removeAttribute( div, 'aria-hidden' ); 12 | expect( div.getAttribute( 'aria-hidden' ) ).toBeNull(); 13 | expect( div.getAttribute( 'tabindex' ) ).not.toBeNull(); 14 | 15 | removeAttribute( div, 'tabindex' ); 16 | expect( div.getAttribute( 'tabindex' ) ).toBeNull(); 17 | } ); 18 | 19 | test( 'can remove attributes from an element.', () => { 20 | const div = document.createElement( 'div' ); 21 | 22 | div.setAttribute( 'aria-hidden', 'true' ); 23 | div.setAttribute( 'tabindex', '-1' ); 24 | 25 | removeAttribute( div, [ 'aria-hidden', 'tabindex' ] ); 26 | expect( div.getAttribute( 'aria-hidden' ) ).toBeNull(); 27 | expect( div.getAttribute( 'tabindex' ) ).toBeNull(); 28 | } ); 29 | 30 | test( 'can remove attributes from elements.', () => { 31 | const div1 = document.createElement( 'div1' ); 32 | const div2 = document.createElement( 'div2' ); 33 | const div3 = document.createElement( 'div2' ); 34 | const divs = [ div1, div2, div3 ]; 35 | const callback = jest.fn(); 36 | 37 | divs.forEach( div => { 38 | div.setAttribute( 'aria-hidden', 'true' ); 39 | div.setAttribute( 'tabindex', '-1' ); 40 | } ); 41 | 42 | removeAttribute( divs, [ 'aria-hidden', 'tabindex' ] ); 43 | 44 | divs.forEach( div => { 45 | expect( div.getAttribute( 'aria-hidden' ) ).toBeNull(); 46 | expect( div.getAttribute( 'tabindex' ) ).toBeNull(); 47 | callback(); 48 | } ); 49 | 50 | expect( callback ).toHaveBeenCalledTimes( divs.length ); 51 | } ); 52 | } ); 53 | -------------------------------------------------------------------------------- /src/js/utils/dom/removeAttribute/removeAttribute.ts: -------------------------------------------------------------------------------- 1 | import { forEach } from '../../array'; 2 | 3 | 4 | /** 5 | * Removes attributes from the element. 6 | * 7 | * @param elms - An element or elements. 8 | * @param attrs - An attribute or attributes to remove. 9 | */ 10 | export function removeAttribute( elms: Element | Element[], attrs: string | string[] ): void { 11 | forEach( elms, elm => { 12 | forEach( attrs, attr => { 13 | elm && elm.removeAttribute( attr ); 14 | } ); 15 | } ); 16 | } 17 | -------------------------------------------------------------------------------- /src/js/utils/dom/removeClass/removeClass.test.ts: -------------------------------------------------------------------------------- 1 | import { removeClass } from './removeClass'; 2 | 3 | 4 | describe( 'removeClass', () => { 5 | beforeEach( () => { 6 | document.body.innerHTML = '
    '; 7 | } ); 8 | 9 | test( 'can remove a class from the element.', () => { 10 | const container = document.getElementById( 'container' ); 11 | 12 | container.classList.add( 'active' ); 13 | expect( container.classList.contains( 'active' ) ).toBe( true ); 14 | 15 | removeClass( container, 'active' ); 16 | expect( container.classList.contains( 'active' ) ).toBe( false ); 17 | } ); 18 | 19 | test( 'can remove classes from the element.', () => { 20 | const container = document.getElementById( 'container' ); 21 | 22 | container.classList.add( 'active' ); 23 | container.classList.add( 'visible' ); 24 | 25 | expect( container.classList.contains( 'active' ) ).toBe( true ); 26 | expect( container.classList.contains( 'visible' ) ).toBe( true ); 27 | 28 | removeClass( container, [ 'active', 'visible' ] ); 29 | 30 | expect( container.classList.contains( 'active' ) ).toBe( false ); 31 | expect( container.classList.contains( 'visible' ) ).toBe( false ); 32 | } ); 33 | } ); 34 | -------------------------------------------------------------------------------- /src/js/utils/dom/removeClass/removeClass.ts: -------------------------------------------------------------------------------- 1 | import { toggleClass } from '../toggleClass/toggleClass'; 2 | 3 | 4 | /** 5 | * Removes classes from the element. 6 | * 7 | * @param elm - An element to remove classes from. 8 | * @param classes - Classes to remove. 9 | */ 10 | export function removeClass( elm: Element, classes: string | string[] ): void { 11 | toggleClass( elm, classes, false ); 12 | } 13 | -------------------------------------------------------------------------------- /src/js/utils/dom/setAttribute/setAttribute.ts: -------------------------------------------------------------------------------- 1 | import { forEach } from '../../array'; 2 | import { forOwn } from '../../object'; 3 | import { isNull, isObject } from '../../type/type'; 4 | import { removeAttribute } from '../removeAttribute/removeAttribute'; 5 | 6 | 7 | export function setAttribute( elms: Element | Element[], attr: string, value: string | number | boolean ): void; 8 | export function setAttribute( elms: Element | Element[], attrs: Record ): void; 9 | 10 | /** 11 | * Sets attribute/attributes to the element or elements. 12 | * If the value is `null` or an empty string, the attribute will be removed. 13 | * 14 | * @param elms - An element or an array with elements. 15 | * @param attrs - An attribute name of an object with pairs of a name and a value. 16 | * @param value - A value to set. 17 | */ 18 | export function setAttribute( 19 | elms: Element | Element[], 20 | attrs: string | Record, 21 | value?: string | number | boolean 22 | ): void { 23 | if ( isObject( attrs ) ) { 24 | forOwn( attrs, ( value, name ) => { 25 | setAttribute( elms, name, value ); 26 | } ); 27 | } else { 28 | forEach( elms, elm => { 29 | isNull( value ) || value === '' ? removeAttribute( elm, attrs ) : elm.setAttribute( attrs, String( value ) ); 30 | } ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/js/utils/dom/style/style.test.ts: -------------------------------------------------------------------------------- 1 | import { style } from './style'; 2 | 3 | 4 | describe( 'styles', () => { 5 | test( 'can set an inline style', () => { 6 | const div = document.createElement( 'div' ); 7 | style( div, 'color', 'red' ); 8 | style( div, 'backgroundColor', 'white' ); 9 | style( div, 'fontSize', '1rem' ); 10 | 11 | expect( div.style.color ).toBe( 'red' ); 12 | expect( div.style.backgroundColor ).toBe( 'white' ); 13 | expect( div.style.fontSize ).toBe( '1rem' ); 14 | } ); 15 | 16 | test( 'can return a computed style', () => { 17 | const div = document.createElement( 'div' ); 18 | div.style.color = 'red'; 19 | expect( style( div, 'color' ) ).toBe( 'red' ); 20 | } ); 21 | } ); 22 | -------------------------------------------------------------------------------- /src/js/utils/dom/style/style.ts: -------------------------------------------------------------------------------- 1 | import { isNull, isUndefined } from '../../type/type'; 2 | 3 | 4 | export function style( 5 | elm: HTMLElement, 6 | prop: K, 7 | ): CSSStyleDeclaration[ K ]; 8 | 9 | export function style( 10 | elm: HTMLElement, 11 | prop: string, 12 | ): string; 13 | 14 | export function style( 15 | elm: HTMLElement, 16 | prop: string, 17 | value: string | number 18 | ): void; 19 | 20 | 21 | /** 22 | * Applies inline styles to the provided element by an object literal. 23 | * 24 | * @param elm - An element to apply styles to. 25 | * @param prop - An object literal with styles or a property name. 26 | * @param value - A value to set. 27 | */ 28 | export function style( 29 | elm: HTMLElement, 30 | prop: string, 31 | value?: string | number 32 | ): string | void { 33 | if ( isUndefined( value ) ) { 34 | return getComputedStyle( elm )[ prop ]; 35 | } 36 | 37 | if ( ! isNull( value ) ) { 38 | elm.style[ prop ] = `${ value }`; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/js/utils/dom/timeOf/timeOf.test.ts: -------------------------------------------------------------------------------- 1 | import { fire } from '../../../test'; 2 | import { timeOf } from './timeOf'; 3 | 4 | 5 | describe( 'timeOf', () => { 6 | test( 'can extract a timestamp from an event object.', done => { 7 | window.addEventListener( 'click', e => { 8 | expect( timeOf( e ) ).toBe( 123 ); 9 | done(); 10 | } ); 11 | 12 | fire( window, 'click', { timeStamp: 123 } ); 13 | } ); 14 | } ); -------------------------------------------------------------------------------- /src/js/utils/dom/timeOf/timeOf.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Extracts the timestamp from the event object. 3 | * 4 | * @param e - An Event object. 5 | */ 6 | export function timeOf( e: Event ): number { 7 | return e.timeStamp; 8 | } -------------------------------------------------------------------------------- /src/js/utils/dom/toggleClass/toggleClass.test.ts: -------------------------------------------------------------------------------- 1 | import { toggleClass } from './toggleClass'; 2 | 3 | 4 | describe( 'toggleClass', () => { 5 | beforeEach( () => { 6 | document.body.innerHTML = '
    '; 7 | } ); 8 | 9 | test( 'can add a class to the element.', () => { 10 | const container = document.getElementById( 'container' ); 11 | toggleClass( container, 'active', true ); 12 | expect( container.classList.contains( 'active' ) ).toBe( true ); 13 | } ); 14 | 15 | test( 'can add classes to the element.', () => { 16 | const container = document.getElementById( 'container' ); 17 | 18 | toggleClass( container, [ 'active', 'visible' ], true ); 19 | 20 | expect( container.classList.contains( 'active' ) ).toBe( true ); 21 | expect( container.classList.contains( 'visible' ) ).toBe( true ); 22 | } ); 23 | 24 | test( 'can remove a class from the element.', () => { 25 | const container = document.getElementById( 'container' ); 26 | container.classList.add( 'active' ); 27 | expect( container.classList.contains( 'active' ) ).toBe( true ); 28 | 29 | toggleClass( container, 'active', false ); 30 | expect( container.classList.contains( 'active' ) ).toBe( false ); 31 | } ); 32 | 33 | test( 'can remove classes from the element.', () => { 34 | const container = document.getElementById( 'container' ); 35 | container.classList.add( 'active' ); 36 | container.classList.add( 'visible' ); 37 | 38 | expect( container.classList.contains( 'active' ) ).toBe( true ); 39 | expect( container.classList.contains( 'visible' ) ).toBe( true ); 40 | 41 | toggleClass( container, [ 'active', 'visible' ], false ); 42 | 43 | expect( container.classList.contains( 'active' ) ).toBe( false ); 44 | expect( container.classList.contains( 'visible' ) ).toBe( false ); 45 | } ); 46 | } ); 47 | -------------------------------------------------------------------------------- /src/js/utils/dom/toggleClass/toggleClass.ts: -------------------------------------------------------------------------------- 1 | import { forEach } from '../../array'; 2 | 3 | 4 | /** 5 | * Toggles the provided class or classes by following the `add` boolean. 6 | * 7 | * @param elm - An element whose classes are toggled. 8 | * @param classes - A class or class names. 9 | * @param add - Whether to add or remove a class. 10 | */ 11 | export function toggleClass( elm: Element, classes: string | string[], add: boolean ): void { 12 | if ( elm ) { 13 | forEach( classes, name => { 14 | if ( name ) { 15 | elm.classList[ add ? 'add' : 'remove' ]( name ); 16 | } 17 | } ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/js/utils/dom/unit/unit.test.ts: -------------------------------------------------------------------------------- 1 | import { unit } from './unit'; 2 | 3 | 4 | describe( 'unit', () => { 5 | test( 'can append `px` if the value is number.', () => { 6 | expect( unit( 1 ) ).toBe( '1px' ); 7 | expect( unit( 1.8 ) ).toBe( '1.8px' ); 8 | } ); 9 | 10 | test( 'should return the value itself if it is string.', () => { 11 | expect( unit( '10vh' ) ).toBe( '10vh' ); 12 | expect( unit( '10em' ) ).toBe( '10em' ); 13 | } ); 14 | } ); 15 | -------------------------------------------------------------------------------- /src/js/utils/dom/unit/unit.ts: -------------------------------------------------------------------------------- 1 | import { isString } from '../../type/type'; 2 | 3 | 4 | /** 5 | * Appends `px` to the provided number. 6 | * If the value is already string, just returns it. 7 | * 8 | * @param value - A value to append `px` to. 9 | * 10 | * @return A string with the CSS unit. 11 | */ 12 | export function unit( value: number | string ): string { 13 | return isString( value ) ? value : value ? `${ value }px` : ''; 14 | } 15 | -------------------------------------------------------------------------------- /src/js/utils/error/assert/assert.ts: -------------------------------------------------------------------------------- 1 | import { PROJECT_CODE } from '../../../constants/project'; 2 | 3 | 4 | /** 5 | * Throws an error if the provided condition is falsy. 6 | * 7 | * @param condition - If falsy, an error is thrown. 8 | * @param message - Optional. A message to display. 9 | */ 10 | export function assert( condition: any, message?: string ): void { 11 | if ( ! condition ) { 12 | throw new Error( `[${ PROJECT_CODE }] ${ message || '' }` ); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/js/utils/error/error/error.ts: -------------------------------------------------------------------------------- 1 | import { PROJECT_CODE } from '../../../constants/project'; 2 | 3 | 4 | /** 5 | * Displays the error message on the console. 6 | * 7 | * @param message - A message. 8 | */ 9 | export function error( message: string ): void { 10 | console.error( `[${ PROJECT_CODE }] ${ message }` ); 11 | } 12 | -------------------------------------------------------------------------------- /src/js/utils/error/index.ts: -------------------------------------------------------------------------------- 1 | export { assert } from './assert/assert'; 2 | export { error } from './error/error'; 3 | -------------------------------------------------------------------------------- /src/js/utils/function/apply/apply.test.ts: -------------------------------------------------------------------------------- 1 | import { apply } from './apply'; 2 | 3 | 4 | describe( 'apply', () => { 5 | test( 'can bind arguments to the function.', () => { 6 | function sum( a: number, b: number, c = 0, d = 0 ): number { 7 | return a + b + c + d; 8 | } 9 | 10 | // The type should be ( b: number, c?: number, d?: number ) => number. 11 | const sum1 = apply( sum, 1 ); 12 | const sum2 = apply( sum, 1, 1 ); 13 | const sum3 = apply( sum, 1, 1, 1 ); 14 | const sum4 = apply( sum, 1, 1, 1, 1 ); 15 | 16 | expect( sum1( 1, 1, 1 ) ).toBe( 4 ); 17 | expect( sum2( 1, 1 ) ).toBe( 4 ); 18 | expect( sum3( 1 ) ).toBe( 4 ); 19 | expect( sum4() ).toBe( 4 ); 20 | 21 | expect( sum1( 2 ) ).toBe( 3 ); // 1, 2, 0, 0 22 | expect( sum1( 2, 2 ) ).toBe( 5 ); // 1, 2, 2, 0 23 | expect( sum1( 2, 2, 2 ) ).toBe( 7 ); // 1, 2, 2, 2 24 | } ); 25 | } ); -------------------------------------------------------------------------------- /src/js/utils/function/apply/apply.ts: -------------------------------------------------------------------------------- 1 | import { AnyFunction, ShiftN } from '../../../types'; 2 | import { slice } from '../../arrayLike'; 3 | 4 | 5 | /** 6 | * Create a function where provided arguments are bound. 7 | * `this` parameter will be always null. 8 | * 9 | * @param func - A function. 10 | * @param args - Arguments to bind to the function. 11 | * 12 | * @return A function where arguments are bound. 13 | */ 14 | export function apply( 15 | func: F, 16 | ...args: A 17 | ): ( ...args: ShiftN, A["length"]> ) => ReturnType; 18 | 19 | /** 20 | * Create a function where provided arguments are bound. 21 | * `this` parameter will be always null. 22 | * 23 | * @param func - A function. 24 | */ 25 | export function apply( func: AnyFunction ): any { 26 | // eslint-disable-next-line prefer-rest-params, prefer-spread 27 | return func.bind( null, ...slice( arguments, 1 ) ); 28 | } 29 | -------------------------------------------------------------------------------- /src/js/utils/function/index.ts: -------------------------------------------------------------------------------- 1 | export { apply } from './apply/apply'; 2 | export { nextTick } from './nextTick/nextTick'; 3 | export { noop } from './noop/noop'; 4 | export { raf } from './raf/raf'; 5 | -------------------------------------------------------------------------------- /src/js/utils/function/nextTick/nextTick.ts: -------------------------------------------------------------------------------- 1 | import { AnyFunction } from '../../../types'; 2 | 3 | 4 | /** 5 | * Invokes the callback on the next tick. 6 | * 7 | * @param callback - A callback function. 8 | */ 9 | export const nextTick: ( callback: AnyFunction ) => ReturnType = setTimeout; 10 | -------------------------------------------------------------------------------- /src/js/utils/function/noop/noop.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * No operation. 3 | */ 4 | export const noop = (): void => {}; // eslint-disable-line no-empty-function, @typescript-eslint/no-empty-function 5 | -------------------------------------------------------------------------------- /src/js/utils/function/raf/raf.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The alias of `window.requestAnimationFrame()`. 3 | */ 4 | export function raf( func: FrameRequestCallback ): number { 5 | return requestAnimationFrame( func ); 6 | } 7 | -------------------------------------------------------------------------------- /src/js/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './array'; 2 | export * from './arrayLike'; 3 | export * from './dom'; 4 | export * from './error'; 5 | export * from './function'; 6 | export * from './math'; 7 | export * from './object'; 8 | export * from './string'; 9 | export * from './type/type'; 10 | -------------------------------------------------------------------------------- /src/js/utils/math/approximatelyEqual/approximatelyEqual.test.ts: -------------------------------------------------------------------------------- 1 | import { approximatelyEqual } from './approximatelyEqual'; 2 | 3 | 4 | describe( 'approximatelyEqual', () => { 5 | test( 'can tell if 2 numbers are approximately equal or not.', () => { 6 | expect( approximatelyEqual( 1, 1, 1 ) ).toBe( true ); 7 | expect( approximatelyEqual( 1, 0.9, 1 ) ).toBe( true ); 8 | expect( approximatelyEqual( 1, 1.9, 1 ) ).toBe( true ); 9 | 10 | expect( approximatelyEqual( 1, 2, 1 ) ).toBe( false ); 11 | expect( approximatelyEqual( 1, 0, 1 ) ).toBe( false ); 12 | 13 | expect( approximatelyEqual( 1, 2, 2 ) ).toBe( true ); 14 | expect( approximatelyEqual( 1, 0, 2 ) ).toBe( true ); 15 | } ); 16 | } ); 17 | -------------------------------------------------------------------------------- /src/js/utils/math/approximatelyEqual/approximatelyEqual.ts: -------------------------------------------------------------------------------- 1 | import { abs } from '../math/math'; 2 | 3 | 4 | /** 5 | * Checks if the provided 2 numbers are approximately equal or not. 6 | * 7 | * @param x - A number. 8 | * @param y - Another number to compare. 9 | * @param epsilon - An accuracy that defines the approximation. 10 | * 11 | * @return `true` if 2 numbers are considered to be equal, or otherwise `false`. 12 | */ 13 | export function approximatelyEqual( x: number, y: number, epsilon: number ): boolean { 14 | return abs( x - y ) < epsilon; 15 | } 16 | -------------------------------------------------------------------------------- /src/js/utils/math/between/between.test.ts: -------------------------------------------------------------------------------- 1 | import { between } from './between'; 2 | 3 | 4 | describe( 'between', () => { 5 | test( 'can check a number is between 2 numbers inclusively.', () => { 6 | expect( between( 0, 0, 1 ) ).toBe( true ); 7 | expect( between( 1, 0, 1 ) ).toBe( true ); 8 | 9 | expect( between( 1, 2, 3 ) ).toBe( false ); 10 | 11 | expect( between( 1, 0, 2 ) ).toBe( true ); 12 | expect( between( 1, 2, 0 ) ).toBe( true ); 13 | } ); 14 | 15 | test( 'can check a number is between 2 numbers exclusively.', () => { 16 | expect( between( 0, 0, 1, true ) ).toBe( false ); 17 | expect( between( 1, 0, 1, true ) ).toBe( false ); 18 | 19 | expect( between( 1, 2, 3, true ) ).toBe( false ); 20 | 21 | expect( between( 1, 0, 2, true ) ).toBe( true ); 22 | expect( between( 1, 2, 0, true ) ).toBe( true ); 23 | } ); 24 | } ); 25 | -------------------------------------------------------------------------------- /src/js/utils/math/between/between.ts: -------------------------------------------------------------------------------- 1 | import { max, min } from '../math/math'; 2 | 3 | 4 | /** 5 | * Checks if the subject number is between `x` and `y`. 6 | * 7 | * @param number - A subject number to check. 8 | * @param x - A min or max number. 9 | * @param y - A max or min number. 10 | * @param exclusive - Optional. Whether to exclude `x` or `y`. 11 | */ 12 | export function between( number: number, x: number, y: number, exclusive?: boolean ): boolean { 13 | const minimum = min( x, y ); 14 | const maximum = max( x, y ); 15 | return exclusive 16 | ? minimum < number && number < maximum 17 | : minimum <= number && number <= maximum; 18 | } 19 | -------------------------------------------------------------------------------- /src/js/utils/math/clamp/clamp.test.ts: -------------------------------------------------------------------------------- 1 | import { clamp } from './clamp'; 2 | 3 | 4 | describe( 'clamp', () => { 5 | test( 'can clamp a number', () => { 6 | expect( clamp( 0, 0, 1 ) ).toBe( 0 ); 7 | expect( clamp( 1, 0, 1 ) ).toBe( 1 ); 8 | 9 | expect( clamp( 1, 2, 3 ) ).toBe( 2 ); 10 | expect( clamp( 1, 0, 0 ) ).toBe( 0 ); 11 | 12 | expect( clamp( 3, 0, 1 ) ).toBe( 1 ); 13 | expect( clamp( 3, 4, 5 ) ).toBe( 4 ); 14 | } ); 15 | } ); 16 | -------------------------------------------------------------------------------- /src/js/utils/math/clamp/clamp.ts: -------------------------------------------------------------------------------- 1 | import { max, min } from '../math/math'; 2 | 3 | 4 | /** 5 | * Clamps a number. 6 | * 7 | * @param number - A subject number to check. 8 | * @param x - A min or max number. 9 | * @param y - A min or max number. 10 | * 11 | * @return A clamped number. 12 | */ 13 | export function clamp( number: number, x: number, y: number ): number { 14 | const minimum = min( x, y ); 15 | const maximum = max( x, y ); 16 | return min( max( minimum, number ), maximum ); 17 | } 18 | -------------------------------------------------------------------------------- /src/js/utils/math/index.ts: -------------------------------------------------------------------------------- 1 | export { approximatelyEqual } from './approximatelyEqual/approximatelyEqual'; 2 | export { between } from './between/between'; 3 | export { clamp } from './clamp/clamp'; 4 | export { sign } from './sign/sign'; 5 | 6 | export * from './math/math'; 7 | -------------------------------------------------------------------------------- /src/js/utils/math/math/math.ts: -------------------------------------------------------------------------------- 1 | export const { min, max, floor, ceil, abs } = Math; 2 | -------------------------------------------------------------------------------- /src/js/utils/math/sign/sign.test.ts: -------------------------------------------------------------------------------- 1 | import { sign } from './sign'; 2 | 3 | 4 | describe( 'sign', () => { 5 | test( 'can return the sign of the number', () => { 6 | expect( sign( 0 ) ).toBe( 0 ); 7 | expect( sign( 1 ) ).toBe( 1 ); 8 | expect( sign( -1 ) ).toBe( -1 ); 9 | 10 | expect( sign( 100 ) ).toBe( 1 ); 11 | expect( sign( -100 ) ).toBe( -1 ); 12 | 13 | expect( sign( 0.5 ) ).toBe( 1 ); 14 | expect( sign( -0.5 ) ).toBe( -1 ); 15 | 16 | expect( sign( Infinity ) ).toBe( 1 ); 17 | expect( sign( -Infinity ) ).toBe( -1 ); 18 | } ); 19 | } ); 20 | -------------------------------------------------------------------------------- /src/js/utils/math/sign/sign.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the sign of the provided number. 3 | * 4 | * @param x - A number. 5 | * 6 | * @return `1` for positive numbers, `-1` for negative numbers, or `0` for `0`. 7 | */ 8 | export function sign( x: number ): number { 9 | return +( x > 0 ) - +( x < 0 ); 10 | } 11 | -------------------------------------------------------------------------------- /src/js/utils/object/assign/assign.test.ts: -------------------------------------------------------------------------------- 1 | import { assign } from './assign'; 2 | 3 | 4 | describe( 'assign', () => { 5 | test( 'can assign own enumerable properties of the source object to the target.', () => { 6 | const object = { a: 1, b: '2' }; 7 | const source = { a: 2, c: true }; 8 | const assigned = assign( object, source ); 9 | 10 | expect( assigned ).toStrictEqual( { a: 2, b: '2', c: true } ); 11 | } ); 12 | 13 | test( 'can assign properties of multiple sources to the target.', () => { 14 | const object = { a: 1, b: '2' }; 15 | const source1 = { a: 2, c: true }; 16 | const source2 = { d: 3, e: '3' }; 17 | const source3 = { e: Infinity }; 18 | const assigned = assign( object, source1, source2, source3 ); 19 | 20 | expect( assigned ).toStrictEqual( { a: 2, b: '2', c: true, d: 3, e: Infinity } ); 21 | } ); 22 | 23 | test( 'should assign a nested object as a reference.', () => { 24 | const object = { a: { b: 1 } }; 25 | const source = { a: { b: 2 } }; 26 | const assigned = assign( object, source ); 27 | 28 | expect( assigned ).toStrictEqual( { a: { b: 2 } } ); 29 | expect( source.a ).toBe( assigned.a ); 30 | } ); 31 | } ); 32 | -------------------------------------------------------------------------------- /src/js/utils/object/assign/assign.ts: -------------------------------------------------------------------------------- 1 | import { Cast, Head, Push, Resolve, Shift } from '../../../types'; 2 | import { slice } from '../../arrayLike'; 3 | import { forOwn } from '../forOwn/forOwn'; 4 | 5 | 6 | /** 7 | * Assigns U to T. 8 | * 9 | * @typeParam T - An object to assign to. 10 | * @typeParam U - An object to assign. 11 | * 12 | * @return An assigned object type. 13 | */ 14 | export type Assign = Omit & U; 15 | 16 | /** 17 | * Recursively assigns U[] to T. 18 | * 19 | * @typeParam T - An object to assign to. 20 | * @typeParam U - A tuple contains objects. 21 | * 22 | * @return An assigned object type. 23 | */ 24 | export type Assigned = { 25 | 0: T, 26 | 1: Assigned>, Shift, N, Push>, 27 | }[ C['length'] extends N ? 0 : 1 ] extends infer A ? Cast : never; 28 | 29 | export function assign( object: T ): T; 30 | 31 | export function assign( 32 | object: T, 33 | ...sources: U 34 | ): Resolve> 35 | 36 | /** 37 | * Assigns all own enumerable properties of all source objects to the provided object. 38 | * 39 | * @param object - An object to assign properties to. 40 | * 41 | * @return An object assigned properties of the sources to. 42 | */ 43 | export function assign( object: T ): any { 44 | // eslint-disable-next-line prefer-rest-params, prefer-spread 45 | slice( arguments, 1 ).forEach( source => { 46 | forOwn( source, ( value, key ) => { 47 | object[ key ] = source[ key ]; 48 | } ); 49 | } ); 50 | 51 | return object; 52 | } 53 | -------------------------------------------------------------------------------- /src/js/utils/object/forOwn/forOwn.test.ts: -------------------------------------------------------------------------------- 1 | import { forOwn } from './forOwn'; 2 | 3 | 4 | describe( 'forOwn', () => { 5 | test( 'can iterate an object by own enumerable properties.', () => { 6 | const object = { a: 1, b: 2, c: 3 }; 7 | let counter = 0; 8 | 9 | forOwn( object, ( value, key ) => { 10 | counter++; 11 | expect( object[ key ] ).toBe( value ); 12 | } ); 13 | 14 | expect( counter ).toBe( Object.keys( object ).length ); 15 | } ); 16 | 17 | test( 'can iterate an object from the end.', () => { 18 | const object = { a: 1, b: 2, c: 3 }; 19 | const values: number[] = []; 20 | 21 | forOwn( object, ( value ) => { 22 | values.push( value ); 23 | }, true ); 24 | 25 | expect( values ).toEqual( [ 3, 2, 1 ] ); 26 | } ); 27 | 28 | test( 'should not handle inherited properties.', () => { 29 | class Constructor { 30 | a = 1; 31 | b = 2; 32 | } 33 | 34 | Constructor.prototype[ 'c' ] = 3; 35 | 36 | const object = {}; 37 | 38 | forOwn( new Constructor(), ( value, key ) => { 39 | object[ key ] = value; 40 | } ); 41 | 42 | expect( object ).toStrictEqual( { a: 1, b: 2 } ); 43 | } ); 44 | } ); 45 | -------------------------------------------------------------------------------- /src/js/utils/object/forOwn/forOwn.ts: -------------------------------------------------------------------------------- 1 | import { ownKeys } from '../ownKeys/ownKeys'; 2 | 3 | 4 | /** 5 | * Iterates over the provided object by own enumerable keys with calling the iteratee function. 6 | * 7 | * @param object - An object to iterate over. 8 | * @param iteratee - An iteratee function that takes `value` and `key` as arguments. 9 | * @param right - If `true`, the method iterates over the object from the end like `forEachRight()`. 10 | * 11 | * @return A provided object itself. 12 | */ 13 | export function forOwn( 14 | object: T, 15 | iteratee: ( value: T[ keyof T ], key: string ) => boolean | void, 16 | right?: boolean 17 | ): T { 18 | if ( object ) { 19 | ( right ? ownKeys( object ).reverse() : ownKeys( object ) ).forEach( key => { 20 | key !== '__proto__' && iteratee( object[ key ], key ); 21 | } ); 22 | } 23 | 24 | return object; 25 | } 26 | -------------------------------------------------------------------------------- /src/js/utils/object/index.ts: -------------------------------------------------------------------------------- 1 | export { assign } from './assign/assign'; 2 | export { forOwn } from './forOwn/forOwn'; 3 | export { merge } from './merge/merge'; 4 | export { omit } from './omit/omit'; 5 | export { ownKeys } from './ownKeys/ownKeys'; 6 | -------------------------------------------------------------------------------- /src/js/utils/object/merge/merge.test.ts: -------------------------------------------------------------------------------- 1 | import { merge } from './merge'; 2 | 3 | 4 | describe( 'merge', () => { 5 | test( 'can merge 2 objects.', () => { 6 | const object = { a: 1, b: '2' }; 7 | const source = { a: 2, c: true }; 8 | 9 | expect( merge( object, source ) ).toStrictEqual( { a: 2, b: '2', c: true } ); 10 | 11 | // Should not change the source 12 | expect( source ).toStrictEqual( { a: 2, c: true } ); 13 | } ); 14 | 15 | test( 'can merge 2 objects recursively.', () => { 16 | const object = { a: 1, b: { c: 2, d: 3 } }; 17 | const source = { b: { d: 4, e: 5 }, f: true }; 18 | 19 | expect( merge( object, source ) ).toStrictEqual( { 20 | a: 1, 21 | b: { c: 2, d: 4, e: 5 }, 22 | f: true, 23 | } ); 24 | } ); 25 | 26 | test( 'can merge multiple objects recursively.', () => { 27 | const object = { a: 1, b: { c: 2, d: 3 } }; 28 | const source1 = { b: { d: 4, e: 5 }, f: true }; 29 | const source2 = { b: { d: '4', g: 6 }, h: [ 1, 2, 3 ] }; 30 | const source3 = { h: [ 4, 5, 6 ], i: Infinity }; 31 | const source4 = { a: '1' }; 32 | const merged = merge( object, source1, source2, source3, source4 ); 33 | 34 | expect( merged ).toStrictEqual( { 35 | a: '1', 36 | b: { c: 2, d: '4', e: 5, g: 6 }, 37 | f: true, 38 | h: [ 4, 5, 6 ], 39 | i: Infinity, 40 | } ); 41 | } ); 42 | 43 | test( 'should disconnect reference of arrays.', () => { 44 | const array = [ 1, 2, 3 ]; 45 | const object = {}; 46 | const source = { array }; 47 | const merged = merge( object, source ); 48 | 49 | expect( merged ).toStrictEqual( { array: [ 1, 2, 3 ] } ); 50 | expect( merged.array ).not.toBe( array ); 51 | } ); 52 | } ); 53 | -------------------------------------------------------------------------------- /src/js/utils/object/omit/omit.test.ts: -------------------------------------------------------------------------------- 1 | import { omit } from './omit'; 2 | 3 | 4 | describe( 'omit', () => { 5 | function hasOwn( object: object, key: string ): boolean { 6 | return Object.prototype.hasOwnProperty.call( object, key ); 7 | } 8 | 9 | test( 'can delete specified key.', () => { 10 | const object = { a: 1, b: 2, c: 3 }; 11 | 12 | expect( hasOwn( object, 'a' ) ).toBe( true ); 13 | expect( hasOwn( object, 'b' ) ).toBe( true ); 14 | 15 | omit( object, 'a' ); 16 | expect( hasOwn( object, 'a' ) ).toBe( false ); 17 | 18 | omit( object, 'b' ); 19 | expect( hasOwn( object, 'b' ) ).toBe( false ); 20 | } ); 21 | 22 | test( 'can delete specified keys.', () => { 23 | const object = { a: 1, b: 2, c: 3 }; 24 | 25 | omit( object, [ 'a', 'b' ] ); 26 | expect( hasOwn( object, 'a' ) ).toBe( false ); 27 | expect( hasOwn( object, 'b' ) ).toBe( false ); 28 | } ); 29 | 30 | test( 'can delete all own enumerable keys.', () => { 31 | const object = { a: 1, b: 2, c: 3 }; 32 | 33 | omit( object ); 34 | expect( hasOwn( object, 'a' ) ).toBe( false ); 35 | expect( hasOwn( object, 'b' ) ).toBe( false ); 36 | expect( hasOwn( object, 'c' ) ).toBe( false ); 37 | expect( Object.keys( object ).length ).toBe( 0 ); 38 | } ); 39 | 40 | test( 'should not delete inherited keys.', () => { 41 | const parent = { a: 1, b: 2, c: 3 }; 42 | const object = Object.create( parent ); 43 | 44 | omit( object ); 45 | 46 | expect( hasOwn( parent, 'a' ) ).toBe( true ); 47 | expect( hasOwn( parent, 'b' ) ).toBe( true ); 48 | expect( hasOwn( parent, 'c' ) ).toBe( true ); 49 | 50 | expect( object.a ).toBe( 1 ); 51 | expect( object.b ).toBe( 2 ); 52 | expect( object.c ).toBe( 3 ); 53 | } ); 54 | } ); 55 | -------------------------------------------------------------------------------- /src/js/utils/object/omit/omit.ts: -------------------------------------------------------------------------------- 1 | import { forEach } from '../../array'; 2 | import { ownKeys } from '../ownKeys/ownKeys'; 3 | 4 | 5 | /** 6 | * Deletes specified own keys from the object. 7 | * 8 | * @param object - An object. 9 | * @param keys - A key or keys to delete. If not specified, all own enumerable keys will be deleted. 10 | */ 11 | export function omit( object: object, keys?: string | string[] ): void { 12 | forEach( keys || ownKeys( object ), key => { 13 | delete object[ key ]; 14 | } ); 15 | } -------------------------------------------------------------------------------- /src/js/utils/object/ownKeys/ownKeys.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * An alias of `Object.keys()` 3 | */ 4 | export const ownKeys = Object.keys; -------------------------------------------------------------------------------- /src/js/utils/string/camelToKebab/camelToKebab.test.ts: -------------------------------------------------------------------------------- 1 | import { camelToKebab } from './camelToKebab'; 2 | 3 | 4 | describe( 'camelToKebab', () => { 5 | test( 'can convert a string in the camel case to the kebab case.', () => { 6 | expect( camelToKebab( 'maxWidth' ) ).toBe( 'max-width' ); 7 | expect( camelToKebab( 'borderLeftWidth' ) ).toBe( 'border-left-width' ); 8 | expect( camelToKebab( 'listStyleType' ) ).toBe( 'list-style-type' ); 9 | 10 | expect( camelToKebab( 'ButtonElement' ) ).toBe( 'button-element' ); 11 | } ); 12 | 13 | test( 'should do nothing if the string is already described in the kebab case.', () => { 14 | expect( camelToKebab( 'max-width' ) ).toBe( 'max-width' ); 15 | expect( camelToKebab( 'border-left-width' ) ).toBe( 'border-left-width' ); 16 | } ); 17 | } ); 18 | -------------------------------------------------------------------------------- /src/js/utils/string/camelToKebab/camelToKebab.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Converts the provided string in the camel case to the kebab case. 3 | * 4 | * @param string - A string to convert. 5 | */ 6 | export function camelToKebab( string: string ): string { 7 | return string.replace( /([a-z0-9])([A-Z])/g, '$1-$2' ).toLowerCase(); 8 | } 9 | -------------------------------------------------------------------------------- /src/js/utils/string/format/format.test.ts: -------------------------------------------------------------------------------- 1 | import { format } from './format'; 2 | 3 | 4 | describe( 'format', () => { 5 | test( 'can replace %s with provided replacements', () => { 6 | expect( format( '%s results', 10 ) ).toBe( '10 results' ); 7 | expect( format( '%s/%s', [ 1, 10 ] ) ).toBe( '1/10' ); 8 | } ); 9 | } ); 10 | -------------------------------------------------------------------------------- /src/js/utils/string/format/format.ts: -------------------------------------------------------------------------------- 1 | import { forEach } from '../../array'; 2 | 3 | 4 | /** 5 | * Formats a string. 6 | * 7 | * @param string - A string to format. 8 | * @param replacements - A replacement or replacements. 9 | * 10 | * @return A formatted string. 11 | */ 12 | export function format( string: string, replacements: string | number | Array ): string { 13 | forEach( replacements, replacement => { 14 | string = string.replace( '%s', `${ replacement }` ); 15 | } ); 16 | 17 | return string; 18 | } 19 | -------------------------------------------------------------------------------- /src/js/utils/string/index.ts: -------------------------------------------------------------------------------- 1 | export { camelToKebab } from './camelToKebab/camelToKebab'; 2 | export { format } from './format/format'; 3 | export { pad } from './pad/pad'; 4 | export { uniqueId } from './uniqueId/uniqueId'; 5 | -------------------------------------------------------------------------------- /src/js/utils/string/pad/pad.test.ts: -------------------------------------------------------------------------------- 1 | import { pad } from './pad'; 2 | 3 | 4 | describe( 'pad', () => { 5 | test( 'can pad a number with 0.', () => { 6 | expect( pad( 1 ) ).toBe( '01' ); 7 | expect( pad( 5 ) ).toBe( '05' ); 8 | } ); 9 | 10 | test( 'should not pad if the number is greater than 9.', () => { 11 | expect( pad( 10 ) ).toBe( '10' ); 12 | expect( pad( 11 ) ).toBe( '11' ); 13 | } ); 14 | } ); 15 | -------------------------------------------------------------------------------- /src/js/utils/string/pad/pad.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Pads the number with 0. 3 | * 4 | * @param number - A number to pad. 5 | * 6 | * @return string - Padded number. 7 | */ 8 | export function pad( number: number ): string { 9 | return number < 10 ? `0${ number }` : `${ number }`; 10 | } 11 | -------------------------------------------------------------------------------- /src/js/utils/string/uniqueId/uniqueId.test.ts: -------------------------------------------------------------------------------- 1 | import { uniqueId } from './uniqueId'; 2 | 3 | 4 | describe( 'uniqueId', () => { 5 | test( 'can generate a sequential unique ID.', () => { 6 | expect( uniqueId( 'container-' ) ).toBe( 'container-01' ); 7 | expect( uniqueId( 'container-' ) ).toBe( 'container-02' ); 8 | 9 | expect( uniqueId( 'button-' ) ).toBe( 'button-01' ); 10 | expect( uniqueId( 'button-' ) ).toBe( 'button-02' ); 11 | 12 | expect( uniqueId( 'container-' ) ).toBe( 'container-03' ); 13 | expect( uniqueId( 'container-' ) ).toBe( 'container-04' ); 14 | 15 | expect( uniqueId( 'button-' ) ).toBe( 'button-03' ); 16 | expect( uniqueId( 'button-' ) ).toBe( 'button-04' ); 17 | } ); 18 | } ); 19 | -------------------------------------------------------------------------------- /src/js/utils/string/uniqueId/uniqueId.ts: -------------------------------------------------------------------------------- 1 | import { pad } from '../pad/pad'; 2 | 3 | 4 | /** 5 | * Stores unique IDs. 6 | * 7 | * @since 3.0.0 8 | */ 9 | const ids: Record = {}; 10 | 11 | /** 12 | * Returns a sequential unique ID as "{ prefix }-{ number }". 13 | * 14 | * @param prefix - A prefix for the ID. 15 | */ 16 | export function uniqueId( prefix: string ): string { 17 | return `${ prefix }${ pad( ( ids[ prefix ] = ( ids[ prefix ] || 0 ) + 1 ) ) }`; 18 | } 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "target": "es6", 5 | "sourceMap": true, 6 | "mapRoot": "./", 7 | "moduleResolution": "node", 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "strict": true, 11 | "strictNullChecks": false, 12 | "suppressImplicitAnyIndexErrors": true, 13 | "declarationDir": "./dist/types", 14 | "declaration": true, 15 | "lib": [ 16 | "dom", 17 | "es6" 18 | ], 19 | }, 20 | "include": [ 21 | "src/js/**/*.ts", 22 | ], 23 | "exclude": [ 24 | "node_modules", 25 | "src/js/**/*.test.ts" 26 | ] 27 | } 28 | --------------------------------------------------------------------------------