Splide.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  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, SyncTarget } 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. * An array with SyncTarget objects for splide instances to sync with.
  45. */
  46. readonly splides: SyncTarget[] = [];
  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( { splide: this, isChild: true } );
  127. if ( this.state.is( IDLE ) ) {
  128. this._Components.Sync.remount();
  129. splide.Components.Sync.remount();
  130. }
  131. return this;
  132. }
  133. /**
  134. * Moves the slider with the following control pattern.
  135. *
  136. * | Pattern | Description |
  137. * |---|---|
  138. * | `i` | Goes to the slide `i` |
  139. * | `'+${i}'` | Increments the slide index by `i` |
  140. * | `'-${i}'` | Decrements the slide index by `i` |
  141. * | `'>'` | Goes to the next page |
  142. * | `'<'` | Goes to the previous page |
  143. * | `>${i}` | Goes to the page `i` |
  144. *
  145. * In most cases, `'>'` and `'<'` notations are enough to control the slider
  146. * because they respect `perPage` and `perMove` options.
  147. *
  148. * @example
  149. * ```ts
  150. * var splide = new Splide();
  151. *
  152. * // Goes to the slide 1:
  153. * splide.go( 1 );
  154. *
  155. * // Increments the index:
  156. * splide.go( '+2' );
  157. *
  158. * // Goes to the next page:
  159. * splide.go( '>' );
  160. *
  161. * // Goes to the page 2:
  162. * splide.go( '>2' );
  163. * ```
  164. *
  165. * @param control - A control pattern.
  166. *
  167. * @return `this`
  168. */
  169. go( control: number | string ): this {
  170. this._Components.Controller.go( control );
  171. return this;
  172. }
  173. /**
  174. * Registers an event handler.
  175. *
  176. * @example
  177. * ```ts
  178. * var splide = new Splide();
  179. *
  180. * // Listens to a single event:
  181. * splide.on( 'move', function() {} );
  182. *
  183. * // Listens to multiple events:
  184. * splide.on( 'move resize', function() {} );
  185. *
  186. * // Appends a namespace:
  187. * splide.on( 'move.myNamespace resize.myNamespace', function() {} );
  188. * ```
  189. *
  190. * @param events - An event name or names separated by spaces. Use a dot(.) to append a namespace.
  191. * @param callback - A callback function.
  192. *
  193. * @return `this`
  194. */
  195. on<K extends keyof EventMap>( events: K, callback: EventMap[ K ] ): this;
  196. on( events: string | string[], callback: EventBusCallback ): this;
  197. on( events: string | string[], callback: EventBusCallback ): this {
  198. this.event.on( events, callback, null, DEFAULT_USER_EVENT_PRIORITY );
  199. return this;
  200. }
  201. /**
  202. * Removes the registered all handlers for the specified event or events.
  203. * If you want to only remove a particular handler, use namespace to identify it.
  204. *
  205. * @example
  206. * ```ts
  207. * var splide = new Splide();
  208. *
  209. * // Removes all handlers assigned to "move":
  210. * splide.off( 'move' );
  211. *
  212. * // Only removes handlers that belong to the specified namespace:
  213. * splide.off( 'move.myNamespace' );
  214. * ```
  215. *
  216. * @param events - An event name or names separated by spaces. Use a dot(.) to append a namespace.
  217. *
  218. * @return `this`
  219. */
  220. off<K extends keyof EventMap>( events: K | K[] | string | string[] ): this {
  221. this.event.off( events );
  222. return this;
  223. }
  224. /**
  225. * Emits an event and triggers registered handlers.
  226. *
  227. * @param event - An event name to emit.
  228. * @param args - Optional. Any number of arguments to pass to handlers.
  229. *
  230. * @return `this`
  231. */
  232. emit<K extends keyof EventMap>( event: K, ...args: Parameters<EventMap[ K ]> ): this;
  233. emit( event: string, ...args: any[] ): this;
  234. emit( event: string ): this {
  235. // eslint-disable-next-line prefer-rest-params, prefer-spread
  236. this.event.emit( event, ...slice( arguments, 1 ) );
  237. return this;
  238. }
  239. /**
  240. * Inserts a slide at the specified position.
  241. *
  242. * @example
  243. * ```ts
  244. * var splide = new Splide();
  245. * splide.mount();
  246. *
  247. * // Adds the slide by the HTML:
  248. * splide.add( '<li></li> );
  249. *
  250. * // or adds the element:
  251. * splide.add( document.createElement( 'li' ) );
  252. * ```
  253. *
  254. * @param slides - A slide element, an HTML string that represents a slide, or an array with them.
  255. * @param index - Optional. An index to insert a slide at.
  256. *
  257. * @return `this`
  258. */
  259. add( slides: string | HTMLElement | Array<string | HTMLElement>, index?: number ): this {
  260. this._Components.Slides.add( slides, index );
  261. return this;
  262. }
  263. /**
  264. * Removes slides that match the matcher
  265. * that can be an index, an array with indices, a selector, or an iteratee function.
  266. *
  267. * @param matcher - An index, an array with indices, a selector string, or an iteratee function.
  268. */
  269. remove( matcher: SlideMatcher ): this {
  270. this._Components.Slides.remove( matcher );
  271. return this;
  272. }
  273. /**
  274. * Checks the slider type.
  275. *
  276. * @param type - A type to test.
  277. *
  278. * @return `true` if the type matches the current one, or otherwise `false`.
  279. */
  280. is( type: string ): boolean {
  281. return this._options.type === type;
  282. }
  283. /**
  284. * Refreshes the slider.
  285. *
  286. * @return `this`
  287. */
  288. refresh(): this {
  289. this.emit( EVENT_REFRESH );
  290. return this;
  291. }
  292. /**
  293. * Destroys the slider.
  294. *
  295. * @param completely - Optional. If `true`, Splide will not remount the slider by breakpoints.
  296. *
  297. * @return `this`
  298. */
  299. destroy( completely = true ): this {
  300. const { event, state } = this;
  301. if ( state.is( CREATED ) ) {
  302. // Postpones destruction requested before the slider becomes ready.
  303. event.on( EVENT_READY, this.destroy.bind( this, completely ), this );
  304. } else {
  305. forOwn( this._Components, component => {
  306. component.destroy && component.destroy( completely );
  307. }, true );
  308. event.emit( EVENT_DESTROY );
  309. event.destroy();
  310. completely && empty( this.splides );
  311. state.set( DESTROYED );
  312. }
  313. return this;
  314. }
  315. /**
  316. * Returns options.
  317. *
  318. * @return An object with the latest options.
  319. */
  320. get options(): Options {
  321. return this._options;
  322. }
  323. /**
  324. * Merges options to the current options and emits `updated` event.
  325. *
  326. * @param options - An object with new options.
  327. */
  328. set options( options: Options ) {
  329. const { _options } = this;
  330. merge( _options, options );
  331. if ( ! this.state.is( CREATED ) ) {
  332. this.emit( EVENT_UPDATED, _options );
  333. }
  334. }
  335. /**
  336. * Returns the number of slides without clones.
  337. *
  338. * @return The number of slides.
  339. */
  340. get length(): number {
  341. return this._Components.Slides.getLength( true );
  342. }
  343. /**
  344. * Returns the active slide index.
  345. *
  346. * @return The active slide index.
  347. */
  348. get index(): number {
  349. return this._Components.Controller.getIndex();
  350. }
  351. }