Splide.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. import * as ComponentConstructors from '../../components';
  2. import { SlideMatcher } from '../../components/Slides/Slides';
  3. import { CLASS_INITIALIZED } from '../../constants/classes';
  4. import { DEFAULTS } from '../../constants/defaults';
  5. import { EVENT_DESTROY, EVENT_MOUNTED, EVENT_READY, EVENT_REFRESH, EVENT_UPDATED } from '../../constants/events';
  6. import { DEFAULT_USER_EVENT_PRIORITY } from '../../constants/priority';
  7. import { CREATED, DESTROYED, IDLE, STATES } from '../../constants/states';
  8. import { FADE } from '../../constants/types';
  9. import { EventBus, EventBusCallback, EventBusObject, State, StateObject } from '../../constructors';
  10. import { Fade, Slide } from '../../transitions';
  11. import { ComponentConstructor, Components, EventMap, Options } from '../../types';
  12. import { addClass, assert, assign, empty, forOwn, isString, merge, query, slice } from '../../utils';
  13. /**
  14. * The frontend class for the Splide slider.
  15. *
  16. * @since 3.0.0
  17. */
  18. export class Splide {
  19. /**
  20. * Changes the default options for all Splide instances.
  21. */
  22. static defaults: Options = {};
  23. /**
  24. * The collection of state numbers.
  25. */
  26. static readonly STATES = STATES;
  27. /**
  28. * The root element where the Splide is applied.
  29. */
  30. readonly root: HTMLElement;
  31. /**
  32. * The EventBusObject object.
  33. */
  34. readonly event: EventBusObject = EventBus();
  35. /**
  36. * The collection of all component objects.
  37. */
  38. readonly Components: Components = {} as Components;
  39. /**
  40. * The StateObject object.
  41. */
  42. readonly state: StateObject = State( CREATED );
  43. /**
  44. * Splide instances to sync with.
  45. */
  46. readonly splides: Splide[] = [];
  47. /**
  48. * The collection of options.
  49. */
  50. private readonly _options: Options = {};
  51. /**
  52. * The collection of all components.
  53. */
  54. private _Components: Components;
  55. /**
  56. * The collection of extensions.
  57. */
  58. private _Extensions: Record<string, ComponentConstructor> = {};
  59. /**
  60. * The Transition component.
  61. */
  62. private _Transition: ComponentConstructor;
  63. /**
  64. * The Splide constructor.
  65. *
  66. * @param target - The selector for the target element, or the element itself.
  67. * @param options - Optional. An object with options.
  68. */
  69. constructor( target: string | HTMLElement, options?: Options ) {
  70. const root = isString( target ) ? query<HTMLElement>( document, target ) : target;
  71. assert( root, `${ root } is invalid.` );
  72. this.root = root;
  73. merge( DEFAULTS, Splide.defaults );
  74. merge( merge( this._options, DEFAULTS ), options || {} );
  75. }
  76. /**
  77. * Initializes the instance.
  78. *
  79. * @param Extensions - Optional. An object with extensions.
  80. * @param Transition - Optional. A Transition component.
  81. *
  82. * @return `this`
  83. */
  84. mount( Extensions?: Record<string, ComponentConstructor>, Transition?: ComponentConstructor ): this {
  85. const { state, Components } = this;
  86. assert( state.is( [ CREATED, DESTROYED ] ), 'Already mounted!' );
  87. state.set( CREATED );
  88. this._Components = Components;
  89. this._Transition = Transition || this._Transition || ( this.is( FADE ) ? Fade : Slide );
  90. this._Extensions = Extensions || this._Extensions;
  91. const Constructors = assign( {}, ComponentConstructors, this._Extensions, { Transition: this._Transition } );
  92. forOwn( Constructors, ( Component, key ) => {
  93. const component = Component( this, Components, this._options );
  94. Components[ key ] = component;
  95. component.setup && component.setup();
  96. } );
  97. forOwn( Components, component => {
  98. component.mount && component.mount();
  99. } );
  100. this.emit( EVENT_MOUNTED );
  101. addClass( this.root, CLASS_INITIALIZED );
  102. state.set( IDLE );
  103. this.emit( EVENT_READY );
  104. return this;
  105. }
  106. /**
  107. * Syncs the slider with the provided one.
  108. * This method must be called before the `mount()`.
  109. *
  110. * @example
  111. * ```ts
  112. * var primary = new Splide();
  113. * var secondary = new Splide();
  114. *
  115. * primary.sync( secondary );
  116. * primary.mount();
  117. * secondary.mount();
  118. * ```
  119. *
  120. * @param splide - A Splide instance to sync with.
  121. *
  122. * @return `this`
  123. */
  124. sync( splide: Splide ): this {
  125. this.splides.push( splide );
  126. splide.splides.push( this );
  127. return this;
  128. }
  129. /**
  130. * Moves the slider with the following control pattern.
  131. *
  132. * | Pattern | Description |
  133. * |---|---|
  134. * | `i` | Goes to the slide `i` |
  135. * | `'+${i}'` | Increments the slide index by `i` |
  136. * | `'-${i}'` | Decrements the slide index by `i` |
  137. * | `'>'` | Goes to the next page |
  138. * | `'<'` | Goes to the previous page |
  139. * | `>${i}` | Goes to the page `i` |
  140. *
  141. * In most cases, `'>'` and `'<'` notations are enough to control the slider
  142. * because they respect `perPage` and `perMove` options.
  143. *
  144. * @example
  145. * ```ts
  146. * var splide = new Splide();
  147. *
  148. * // Goes to the slide 1:
  149. * splide.go( 1 );
  150. *
  151. * // Increments the index:
  152. * splide.go( '+2' );
  153. *
  154. * // Goes to the next page:
  155. * splide.go( '>' );
  156. *
  157. * // Goes to the page 2:
  158. * splide.go( '>2' );
  159. * ```
  160. *
  161. * @param control - A control pattern.
  162. *
  163. * @return `this`
  164. */
  165. go( control: number | string ): this {
  166. this._Components.Controller.go( control );
  167. return this;
  168. }
  169. /**
  170. * Registers an event handler.
  171. *
  172. * @example
  173. * ```ts
  174. * var splide = new Splide();
  175. *
  176. * // Listens to a single event:
  177. * splide.on( 'move', function() {} );
  178. *
  179. * // Listens to multiple events:
  180. * splide.on( 'move resize', function() {} );
  181. *
  182. * // Appends a namespace:
  183. * splide.on( 'move.myNamespace resize.myNamespace', function() {} );
  184. * ```
  185. *
  186. * @param events - An event name or names separated by spaces. Use a dot(.) to append a namespace.
  187. * @param callback - A callback function.
  188. *
  189. * @return `this`
  190. */
  191. on<K extends keyof EventMap>( events: K, callback: EventMap[ K ] ): this;
  192. on( events: string | string[], callback: EventBusCallback ): this {
  193. this.event.on( events, callback, null, DEFAULT_USER_EVENT_PRIORITY );
  194. return this;
  195. }
  196. /**
  197. * Removes the registered all handlers for the specified event or events.
  198. * If you want to only remove a particular handler, use namespace to identify it.
  199. *
  200. * @example
  201. * ```ts
  202. * var splide = new Splide();
  203. *
  204. * // Removes all handlers assigned to "move":
  205. * splide.off( 'move' );
  206. *
  207. * // Only removes handlers that belong to the specified namespace:
  208. * splide.off( 'move.myNamespace' );
  209. * ```
  210. *
  211. * @param events - An event name or names separated by spaces. Use a dot(.) to append a namespace.
  212. *
  213. * @return `this`
  214. */
  215. off<K extends keyof EventMap>( events: K | K[] | string | string[] ): this {
  216. this.event.off( events );
  217. return this;
  218. }
  219. /**
  220. * Emits an event and triggers registered handlers.
  221. *
  222. * @param event - An event name to emit.
  223. * @param args - Optional. Any number of arguments to pass to handlers.
  224. *
  225. * @return `this`
  226. */
  227. emit<K extends keyof EventMap>( event: K, ...args: Parameters<EventMap[ K ]> ): this;
  228. emit( event: string, ...args: any[] ): this;
  229. emit( event: string ): this {
  230. // eslint-disable-next-line prefer-rest-params, prefer-spread
  231. this.event.emit( event, ...slice( arguments, 1 ) );
  232. return this;
  233. }
  234. /**
  235. * Inserts a slide at the specified position.
  236. *
  237. * @example
  238. * ```ts
  239. * var splide = new Splide();
  240. * splide.mount();
  241. *
  242. * // Adds the slide by the HTML:
  243. * splide.add( '<li></li> );
  244. *
  245. * // or adds the element:
  246. * splide.add( document.createElement( 'li' ) );
  247. * ```
  248. *
  249. * @param slides - A slide element, an HTML string that represents a slide, or an array with them.
  250. * @param index - Optional. An index to insert a slide at.
  251. *
  252. * @return `this`
  253. */
  254. add( slides: string | HTMLElement | Array<string | HTMLElement>, index?: number ): this {
  255. this._Components.Slides.add( slides, index );
  256. return this;
  257. }
  258. /**
  259. * Removes slides that match the matcher
  260. * that can be an index, an array with indices, a selector, or an iteratee function.
  261. *
  262. * @param matcher - An index, an array with indices, a selector string, or an iteratee function.
  263. */
  264. remove( matcher: SlideMatcher ): this {
  265. this._Components.Slides.remove( matcher );
  266. return this;
  267. }
  268. /**
  269. * Checks the slider type.
  270. *
  271. * @param type - A type to test.
  272. *
  273. * @return `true` if the type matches the current one, or otherwise `false`.
  274. */
  275. is( type: string ): boolean {
  276. return this._options.type === type;
  277. }
  278. /**
  279. * Refreshes the slider.
  280. *
  281. * @return `this`
  282. */
  283. refresh(): this {
  284. this.emit( EVENT_REFRESH );
  285. return this;
  286. }
  287. /**
  288. * Destroys the slider.
  289. *
  290. * @param completely - Optional. If `true`, Splide will not remount the slider by breakpoints.
  291. *
  292. * @return `this`
  293. */
  294. destroy( completely = true ): this {
  295. const { event, state } = this;
  296. if ( state.is( CREATED ) ) {
  297. // Postpones destruction requested before the slider becomes ready.
  298. event.on( EVENT_READY, this.destroy.bind( this, completely ), this );
  299. } else {
  300. forOwn( this._Components, component => {
  301. component.destroy && component.destroy( completely );
  302. } );
  303. event.emit( EVENT_DESTROY );
  304. event.destroy();
  305. completely && empty( this.splides );
  306. state.set( DESTROYED );
  307. }
  308. return this;
  309. }
  310. /**
  311. * Returns options.
  312. *
  313. * @return An object with the latest options.
  314. */
  315. get options(): Options {
  316. return this._options;
  317. }
  318. /**
  319. * Merges options to the current options and emits `updated` event.
  320. *
  321. * @param options - An object with new options.
  322. */
  323. set options( options: Options ) {
  324. const { _options } = this;
  325. merge( _options, options );
  326. if ( ! this.state.is( CREATED ) ) {
  327. this.emit( EVENT_UPDATED, _options );
  328. }
  329. }
  330. /**
  331. * Returns the number of slides without clones.
  332. *
  333. * @return The number of slides.
  334. */
  335. get length(): number {
  336. return this._Components.Slides.getLength( true );
  337. }
  338. /**
  339. * Returns the active slide index.
  340. *
  341. * @return The active slide index.
  342. */
  343. get index(): number {
  344. return this._Components.Controller.getIndex();
  345. }
  346. }