Splide.ts 10 KB

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