Move.ts 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. import {
  2. EVENT_MOUNTED,
  3. EVENT_MOVE,
  4. EVENT_MOVED,
  5. EVENT_REFRESH, EVENT_REPOSITIONED,
  6. EVENT_RESIZED,
  7. EVENT_UPDATED,
  8. } from '../../constants/events';
  9. import { IDLE, MOVING } from '../../constants/states';
  10. import { FADE, LOOP, SLIDE } from '../../constants/types';
  11. import { EventInterface } from '../../constructors';
  12. import { Splide } from '../../core/Splide/Splide';
  13. import { AnyFunction, BaseComponent, Components, Options } from '../../types';
  14. import { abs, ceil, clamp, isUndefined, rect, sign } from '../../utils';
  15. /**
  16. * The interface for the Move component.
  17. *
  18. * @since 3.0.0
  19. */
  20. export interface MoveComponent extends BaseComponent {
  21. move( dest: number, index: number, prev: number, callback?: AnyFunction ): void;
  22. jump( index: number ): void;
  23. translate( position: number, preventLoop?: boolean ): void;
  24. cancel(): void;
  25. toIndex( position: number ): number;
  26. toPosition( index: number, trimming?: boolean ): number;
  27. getPosition(): number;
  28. getLimit( max: boolean ): number;
  29. isBusy(): boolean;
  30. exceededLimit( max?: boolean | undefined, position?: number ): boolean;
  31. }
  32. /**
  33. * The component for moving the slider.
  34. *
  35. * @since 3.0.0
  36. *
  37. * @param Splide - A Splide instance.
  38. * @param Components - A collection of components.
  39. * @param options - Options.
  40. *
  41. * @return A Move component object.
  42. */
  43. export function Move( Splide: Splide, Components: Components, options: Options ): MoveComponent {
  44. const { on, emit } = EventInterface( Splide );
  45. const { slideSize, getPadding, totalSize, listSize, sliderSize } = Components.Layout;
  46. const { resolve, orient } = Components.Direction;
  47. const { list, track } = Components.Elements;
  48. /**
  49. * Indicates whether the component can move the slider or not.
  50. */
  51. let waiting: boolean;
  52. /**
  53. * Called when the component is mounted.
  54. */
  55. function mount(): void {
  56. if ( ! Splide.is( FADE ) ) {
  57. on( [ EVENT_MOUNTED, EVENT_RESIZED, EVENT_UPDATED, EVENT_REFRESH ], reposition );
  58. } else {
  59. emit( EVENT_REPOSITIONED );
  60. }
  61. }
  62. /**
  63. * Repositions the slider.
  64. * This must be called before the Slide component checks the visibility.
  65. * Do not call `cancel()` here because LazyLoad may emit resize while transitioning.
  66. */
  67. function reposition(): void {
  68. Components.Scroll.cancel();
  69. jump( Splide.index );
  70. emit( EVENT_REPOSITIONED );
  71. }
  72. /**
  73. * Moves the slider to the dest index with the Transition component.
  74. *
  75. * @param dest - A destination index to go to, including clones'.
  76. * @param index - A slide index.
  77. * @param prev - A previous index.
  78. * @param callback - Optional. A callback function invoked after transition ends.
  79. */
  80. function move( dest: number, index: number, prev: number, callback?: AnyFunction ): void {
  81. if ( ! isBusy() ) {
  82. const { set } = Splide.state;
  83. const position = getPosition();
  84. const looping = dest !== index;
  85. waiting = looping || options.waitForTransition;
  86. set( MOVING );
  87. emit( EVENT_MOVE, index, prev, dest );
  88. Components.Transition.start( dest, () => {
  89. looping && jump( index );
  90. waiting = false;
  91. set( IDLE );
  92. emit( EVENT_MOVED, index, prev, dest );
  93. if ( options.trimSpace === 'move' && dest !== prev && position === getPosition() ) {
  94. Components.Controller.go( dest > prev ? '>' : '<', false, callback );
  95. } else {
  96. callback && callback();
  97. }
  98. } );
  99. }
  100. }
  101. /**
  102. * Jumps to the slide at the specified index.
  103. *
  104. * @param index - An index to jump to.
  105. */
  106. function jump( index: number ): void {
  107. translate( toPosition( index, true ) );
  108. }
  109. /**
  110. * Moves the slider to the provided position.
  111. *
  112. * @param position - The position to move to.
  113. * @param preventLoop - Optional. If `true`, sets the provided position as is.
  114. */
  115. function translate( position: number, preventLoop?: boolean ): void {
  116. Components.Style.ruleBy(
  117. list,
  118. 'transform',
  119. `translate${ resolve( 'X' ) }(${ preventLoop ? position : loop( position ) }px)`
  120. );
  121. }
  122. /**
  123. * Loops the provided position if it exceeds bounds.
  124. *
  125. * @param position - A position to loop.
  126. */
  127. function loop( position: number ): number {
  128. if ( ! waiting && Splide.is( LOOP ) ) {
  129. const diff = orient( position - getPosition() );
  130. const exceededMin = exceededLimit( false, position ) && diff < 0;
  131. const exceededMax = exceededLimit( true, position ) && diff > 0;
  132. if ( exceededMin || exceededMax ) {
  133. const excess = position - getLimit( exceededMax );
  134. const size = sliderSize();
  135. position -= sign( excess ) * size * ceil( abs( excess ) / size );
  136. }
  137. }
  138. return position;
  139. }
  140. /**
  141. * Cancels transition.
  142. */
  143. function cancel(): void {
  144. waiting = false;
  145. translate( getPosition() );
  146. Components.Transition.cancel();
  147. }
  148. /**
  149. * Returns the closest index to the position.
  150. *
  151. * @param position - A position to convert.
  152. *
  153. * @return The closest index to the position.
  154. */
  155. function toIndex( position: number ): number {
  156. const Slides = Components.Slides.get();
  157. let index = 0;
  158. let minDistance = Infinity;
  159. for ( let i = 0; i < Slides.length; i++ ) {
  160. const slideIndex = Slides[ i ].index;
  161. const distance = abs( toPosition( slideIndex, true ) - position );
  162. if ( distance < minDistance ) {
  163. minDistance = distance;
  164. index = slideIndex;
  165. } else {
  166. break;
  167. }
  168. }
  169. return index;
  170. }
  171. /**
  172. * Converts the slide index to the position.
  173. *
  174. * @param index - An index to convert.
  175. * @param trimming - Optional. Whether to trim edge spaces or not.
  176. *
  177. * @return The position corresponding with the index.
  178. */
  179. function toPosition( index: number, trimming?: boolean ): number {
  180. const position = orient( totalSize( index - 1 ) - offset( index ) );
  181. return trimming ? trim( position ) : position;
  182. }
  183. /**
  184. * Returns the current position.
  185. *
  186. * @return The position of the list element.
  187. */
  188. function getPosition(): number {
  189. const left = resolve( 'left' );
  190. return rect( list )[ left ] - rect( track )[ left ] + orient( getPadding( false ) );
  191. }
  192. /**
  193. * Trims spaces on the edge of the slider.
  194. *
  195. * @param position - A position to trim.
  196. *
  197. * @return A trimmed position.
  198. */
  199. function trim( position: number ): number {
  200. if ( options.trimSpace && Splide.is( SLIDE ) ) {
  201. position = clamp( position, 0, orient( sliderSize() - listSize() ) );
  202. }
  203. return position;
  204. }
  205. /**
  206. * Returns the offset amount.
  207. *
  208. * @param index - An index.
  209. */
  210. function offset( index: number ): number {
  211. const { focus } = options;
  212. return focus === 'center' ? ( listSize() - slideSize( index, true ) ) / 2 : +focus * slideSize( index ) || 0;
  213. }
  214. /**
  215. * Returns the limit number that the slider can move to.
  216. *
  217. * @param max - Determines whether to return the maximum or minimum limit.
  218. *
  219. * @return The border number.
  220. */
  221. function getLimit( max: boolean ): number {
  222. return toPosition( max ? Components.Controller.getEnd() : 0, !! options.trimSpace );
  223. }
  224. /**
  225. * Checks if the slider can move now or not.
  226. *
  227. * @return `true` if the slider can move, or otherwise `false`.
  228. */
  229. function isBusy(): boolean {
  230. return !! waiting;
  231. }
  232. /**
  233. * Checks if the provided position exceeds the minimum or maximum limit or not.
  234. *
  235. * @param max - Optional. `true` for testing max, `false` for min, and `undefined` for both.
  236. * @param position - Optional. A position to test. If omitted, tests the current position.
  237. *
  238. * @return `true` if the position exceeds the limit, or otherwise `false`.
  239. */
  240. function exceededLimit( max?: boolean | undefined, position?: number ): boolean {
  241. position = isUndefined( position ) ? getPosition() : position;
  242. const exceededMin = max !== true && orient( position ) < orient( getLimit( false ) );
  243. const exceededMax = max !== false && orient( position ) > orient( getLimit( true ) );
  244. return exceededMin || exceededMax;
  245. }
  246. return {
  247. mount,
  248. move,
  249. jump,
  250. translate,
  251. cancel,
  252. toIndex,
  253. toPosition,
  254. getPosition,
  255. getLimit,
  256. isBusy,
  257. exceededLimit,
  258. };
  259. }