Slides.ts 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. import { EVENT_REFRESH, EVENT_RESIZE } from '../../constants/events';
  2. import { AnyFunction, BaseComponent, ComponentConstructor } from '../../types';
  3. import {
  4. addClass,
  5. append,
  6. apply,
  7. before,
  8. between,
  9. CSSProperties,
  10. empty,
  11. forEach as forEachItem,
  12. includes,
  13. isFunction,
  14. isHTMLElement,
  15. isString,
  16. matches,
  17. parseHtml,
  18. queryAll,
  19. removeNode,
  20. toArray,
  21. } from '@splidejs/utils';
  22. import { Slide, SlideComponent } from './Slide';
  23. /**
  24. * The interface for the Slides component.
  25. *
  26. * @since 3.0.0
  27. */
  28. export interface SlidesComponent extends BaseComponent {
  29. update(): void;
  30. register( slide: HTMLElement, index: number, slideIndex: number ): void;
  31. get( excludeClones?: boolean ): SlideComponent[];
  32. getIn( page: number ): SlideComponent[];
  33. getAt( index: number ): SlideComponent | undefined;
  34. add( slide: string | Element | Array<string | Element>, index?: number ): void;
  35. remove( selector: SlideMatcher ): void;
  36. forEach( iteratee: SlidesIteratee, excludeClones?: boolean ): void;
  37. filter( matcher: SlideMatcher ): SlideComponent[];
  38. style( prop: string, value: string | number, useContainer?: boolean ): void
  39. getLength( excludeClones?: boolean ): number;
  40. isEnough(): boolean;
  41. }
  42. /**
  43. * The iteratee function for Slides.
  44. *
  45. * @since 3.0.0
  46. */
  47. export type SlidesIteratee = ( Slide: SlideComponent, index: number, Slides: SlideComponent[] ) => void
  48. /**
  49. * The predicate function for Slides.
  50. *
  51. * @since 3.0.0
  52. */
  53. export type SlidesPredicate = ( Slide: SlideComponent, index: number, Slides: SlideComponent[] ) => any
  54. /**
  55. * The type for filtering SlideComponent objects.
  56. *
  57. * @since 3.0.0
  58. */
  59. export type SlideMatcher = number | number[] | string | SlidesPredicate;
  60. /**
  61. * The component for managing all slides include clones.
  62. *
  63. * @since 3.0.0
  64. *
  65. * @param Splide - A Splide instance.
  66. * @param Components - A collection of components.
  67. * @param options - Options.
  68. * @param event - An EventInterface instance.
  69. *
  70. * @return An Slides component object.
  71. */
  72. export const Slides: ComponentConstructor<SlidesComponent> = ( Splide, Components, options, event ) => {
  73. const { on, emit, bind } = event;
  74. const { slides, list } = Components.Elements;
  75. /**
  76. * Stores all SlideComponent objects.
  77. */
  78. const Slides: SlideComponent[] = [];
  79. /**
  80. * Called when the component is mounted.
  81. */
  82. function mount(): void {
  83. init();
  84. on( EVENT_REFRESH, destroy );
  85. on( EVENT_REFRESH, init );
  86. }
  87. /**
  88. * Initializes the component.
  89. */
  90. function init(): void {
  91. slides.forEach( ( slide, index ) => { register( slide, index, -1 ) } );
  92. }
  93. /**
  94. * Destroys the component.
  95. */
  96. function destroy(): void {
  97. forEach( Slide => { Slide.destroy() } );
  98. empty( Slides );
  99. }
  100. /**
  101. * Manually updates the status of all slides.
  102. */
  103. function update(): void {
  104. forEach( Slide => { Slide.update() } );
  105. }
  106. /**
  107. * Registers a slide element and creates a Slide object.
  108. * Needs to sort every time when a new slide is registered especially for clones.
  109. *
  110. * @param slide - A slide element to register.
  111. * @param index - A slide index.
  112. * @param slideIndex - A slide index for clones. This must be `-1` for regular slides.
  113. */
  114. function register( slide: HTMLElement, index: number, slideIndex: number ): void {
  115. const object = Slide( Splide, index, slideIndex, slide );
  116. object.mount();
  117. Slides.push( object );
  118. Slides.sort( ( Slide1, Slide2 ) => Slide1.index - Slide2.index );
  119. }
  120. /**
  121. * Returns all Slide objects.
  122. *
  123. * @param excludeClones - Optional. Determines whether to exclude clones or not.
  124. *
  125. * @return An array with Slide objects.
  126. */
  127. function get( excludeClones?: boolean ): SlideComponent[] {
  128. return excludeClones ? filter( Slide => ! Slide.isClone ) : Slides;
  129. }
  130. /**
  131. * Returns slides in the specified page.
  132. *
  133. * @param page - A page index.
  134. *
  135. * @return An array with slides that belong to the page.
  136. */
  137. function getIn( page: number ): SlideComponent[] {
  138. const { Controller } = Components;
  139. const index = Controller.toIndex( page );
  140. const max = Controller.hasFocus() ? 1 : options.perPage;
  141. return filter( Slide => between( Slide.index, index, index + max - 1 ) );
  142. }
  143. /**
  144. * Returns a Slide object at the specified index.
  145. *
  146. * @param index - A slide index.
  147. *
  148. * @return A Slide object if available, or otherwise `undefined`.
  149. */
  150. function getAt( index: number ): SlideComponent | undefined {
  151. return filter( index )[ 0 ];
  152. }
  153. /**
  154. * Inserts a slide or slides at a specified index.
  155. *
  156. * @param items - A slide element, an HTML string or an array with them.
  157. * @param index - Optional. An index to insert the slide at. If omitted, appends it to the list.
  158. */
  159. function add( items: string | Element | Array<string | Element>, index?: number ): void {
  160. forEachItem( items, slide => {
  161. if ( isString( slide ) ) {
  162. slide = parseHtml( slide );
  163. }
  164. if ( isHTMLElement( slide ) ) {
  165. const ref = slides[ index ];
  166. ref ? before( ref, slide ) : append( list, slide );
  167. addClass( slide, options.classes.slide );
  168. observeImages( slide, apply( emit, EVENT_RESIZE ) );
  169. }
  170. } );
  171. emit( EVENT_REFRESH );
  172. }
  173. /**
  174. * Removes slides that match the matcher
  175. * that can be an index, an array with indices, a selector, or an iteratee function.
  176. *
  177. * @param matcher - An index, an array with indices, a selector string, or an iteratee function.
  178. */
  179. function remove( matcher: SlideMatcher ): void {
  180. removeNode( filter( matcher ).map( Slide => Slide.slide ) );
  181. emit( EVENT_REFRESH );
  182. }
  183. /**
  184. * Iterates over Slide objects by the iteratee function.
  185. *
  186. * @param iteratee - An iteratee function that takes a Slide object, an index and an array with Slides.
  187. * @param excludeClones - Optional. Determines whether to exclude clones or not.
  188. */
  189. function forEach( iteratee: SlidesIteratee, excludeClones?: boolean ): void {
  190. get( excludeClones ).forEach( iteratee );
  191. }
  192. /**
  193. * Filters Slides by the matcher
  194. * that can be an index, an array with indices, a selector, or a predicate function.
  195. *
  196. * @param matcher - An index, an array with indices, a selector string, or a predicate function.
  197. *
  198. * @return An array with SlideComponent objects.
  199. */
  200. function filter( matcher: SlideMatcher ): SlideComponent[] {
  201. return Slides.filter( isFunction( matcher )
  202. ? matcher
  203. : Slide => isString( matcher )
  204. ? matches( Slide.slide, matcher )
  205. : includes( toArray( matcher ), Slide.index )
  206. );
  207. }
  208. /**
  209. * Adds a CSS rule to all slides or containers.
  210. *
  211. * @param prop - A property name.
  212. * @param value - A CSS value to add.
  213. * @param useContainer - Optional. Determines whether to apply the rule to the container or not.
  214. */
  215. function style( prop: CSSProperties, value: string | number, useContainer?: boolean ): void {
  216. forEach( Slide => { Slide.style( prop, value, useContainer ) } );
  217. }
  218. /**
  219. * Invokes the callback after all images in the element are loaded.
  220. *
  221. * @param elm - An element that may contain images.
  222. * @param callback - A callback function.
  223. */
  224. function observeImages( elm: Element, callback: AnyFunction ): void {
  225. const images = queryAll( elm, 'img' );
  226. let { length } = images;
  227. if ( length ) {
  228. images.forEach( img => {
  229. bind( img, 'load error', () => {
  230. if ( ! --length ) {
  231. callback();
  232. }
  233. } );
  234. } );
  235. } else {
  236. callback();
  237. }
  238. }
  239. /**
  240. * Returns the length of slides.
  241. *
  242. * @param excludeClones - Optional. Determines whether to exclude clones or not.
  243. *
  244. * @return The length of slides.
  245. */
  246. function getLength( excludeClones?: boolean ): number {
  247. return excludeClones ? slides.length : Slides.length;
  248. }
  249. /**
  250. * Checks if the number of slides is over than the `perPage` option, including clones.
  251. *
  252. * @return `true` if there are enough slides, or otherwise `false`.
  253. */
  254. function isEnough(): boolean {
  255. return Slides.length > options.perPage;
  256. }
  257. return {
  258. mount,
  259. destroy,
  260. update,
  261. register,
  262. get,
  263. getIn,
  264. getAt,
  265. add,
  266. remove,
  267. forEach,
  268. filter,
  269. style,
  270. getLength,
  271. isEnough,
  272. };
  273. };