Splide.ts 10 KB

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