Move.ts 8.6 KB

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