Slide.ts 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. import { SlideComponent } from '@splidejs/splide';
  2. import {
  3. ALL_ATTRIBUTES,
  4. ARIA_CONTROLS,
  5. ARIA_CURRENT,
  6. ARIA_HIDDEN,
  7. ARIA_LABEL,
  8. ROLE,
  9. TAB_INDEX,
  10. } from '../../constants/attributes';
  11. import {
  12. CLASS_ACTIVE,
  13. CLASS_CONTAINER,
  14. CLASS_NEXT,
  15. CLASS_PREV,
  16. CLASS_VISIBLE,
  17. STATUS_CLASSES,
  18. } from '../../constants/classes';
  19. import {
  20. EVENT_ACTIVE,
  21. EVENT_CLICK,
  22. EVENT_HIDDEN,
  23. EVENT_INACTIVE,
  24. EVENT_MOUNTED,
  25. EVENT_MOVE,
  26. EVENT_MOVED,
  27. EVENT_RESIZED, EVENT_SCROLLED,
  28. EVENT_SLIDE_KEYDOWN,
  29. EVENT_UPDATED,
  30. EVENT_VISIBLE,
  31. } from '../../constants/events';
  32. import { FADE, SLIDE } from '../../constants/types';
  33. import { Splide } from '../../core/Splide/Splide';
  34. import { EventInterface } from '../../constructors';
  35. import {
  36. abs,
  37. ceil,
  38. child,
  39. floor,
  40. hasClass,
  41. isHTMLButtonElement,
  42. min,
  43. rect,
  44. removeAttribute,
  45. removeClass,
  46. setAttribute,
  47. toggleClass,
  48. } from '../../utils';
  49. import { format, pad } from '../../utils/string';
  50. /**
  51. * The sub component for managing each slide.
  52. *
  53. * @since 3.0.0
  54. *
  55. * @param Splide - A Splide instance.
  56. * @param index - A slide index.
  57. * @param slideIndex - A slide index for clones. This must be `-1` if the slide is not clone.
  58. * @param slide - A slide element.
  59. *
  60. * @return A Slide sub component.
  61. */
  62. export function Slide( Splide: Splide, index: number, slideIndex: number, slide: HTMLElement ): SlideComponent {
  63. const { on, emit, bind, destroy: destroyEvents } = EventInterface( Splide );
  64. const { Components, root, options } = Splide;
  65. const { isNavigation, updateOnMove } = options;
  66. const { resolve } = Components.Direction;
  67. const isClone = slideIndex > -1;
  68. const container = child( slide, `.${ CLASS_CONTAINER }` );
  69. /**
  70. * Called when the component is mounted.
  71. */
  72. function mount( this: SlideComponent ): void {
  73. init();
  74. bind( slide, 'click keydown', e => {
  75. emit( e.type === 'click' ? EVENT_CLICK : EVENT_SLIDE_KEYDOWN, this, e );
  76. } );
  77. on( [ EVENT_MOUNTED, EVENT_MOVED, EVENT_UPDATED, EVENT_RESIZED, EVENT_SCROLLED ], update.bind( this ) );
  78. if ( updateOnMove ) {
  79. on( EVENT_MOVE, onMove );
  80. }
  81. }
  82. /**
  83. * If the `updateOnMove` option is `true`, called when the slider starts moving.
  84. *
  85. * @param next - A next index.
  86. * @param prev - A previous index.
  87. * @param dest - A destination index.
  88. */
  89. function onMove( next: number, prev: number, dest: number ): void {
  90. if ( dest === index ) {
  91. updateActivity.call( this, true );
  92. }
  93. update.call( this );
  94. }
  95. /**
  96. * Initializes the component.
  97. */
  98. function init(): void {
  99. if ( ! isClone ) {
  100. slide.id = `${ root.id }-slide${ pad( index + 1 ) }`;
  101. }
  102. if ( isNavigation ) {
  103. if ( ! isHTMLButtonElement( slide ) ) {
  104. setAttribute( slide, ROLE, 'button' );
  105. }
  106. const idx = isClone ? slideIndex : index;
  107. const label = format( options.i18n.slideX, idx + 1 );
  108. const controls = Splide.splides.map( splide => splide.root.id ).join( ' ' );
  109. setAttribute( slide, ARIA_LABEL, label );
  110. setAttribute( slide, ARIA_CONTROLS, controls );
  111. }
  112. }
  113. /**
  114. * Destroys the component.
  115. */
  116. function destroy(): void {
  117. destroyEvents();
  118. removeClass( slide, STATUS_CLASSES );
  119. removeAttribute( slide, ALL_ATTRIBUTES );
  120. }
  121. /**
  122. * Updates attribute and classes of the slide.
  123. */
  124. function update( this: SlideComponent ): void {
  125. if ( Components.Controller ) {
  126. const { index: currIndex } = Splide;
  127. updateActivity.call( this, isActive() );
  128. updateVisibility.call( this, isVisible() );
  129. toggleClass( slide, CLASS_PREV, index === currIndex - 1 );
  130. toggleClass( slide, CLASS_NEXT, index === currIndex + 1 );
  131. }
  132. }
  133. /**
  134. * Updates the status related with activity.
  135. *
  136. * @param active - Set `true` if the slide is active.
  137. */
  138. function updateActivity( this: SlideComponent, active: boolean ): void {
  139. toggleClass( slide, CLASS_ACTIVE, active );
  140. if ( active ) {
  141. if ( ! hasClass( slide, CLASS_ACTIVE ) ) {
  142. isNavigation && setAttribute( slide, ARIA_CURRENT, true );
  143. emit( EVENT_ACTIVE, this );
  144. }
  145. } else {
  146. if ( hasClass( slide, CLASS_ACTIVE ) ) {
  147. removeAttribute( slide, ARIA_CURRENT );
  148. emit( EVENT_INACTIVE, this );
  149. }
  150. }
  151. }
  152. /**
  153. * Updates the status related with visibility.
  154. *
  155. * @param visible - Set `true` if the slide is visible.
  156. */
  157. function updateVisibility( this: SlideComponent, visible: boolean ): void {
  158. toggleClass( slide, CLASS_VISIBLE, visible );
  159. setAttribute( slide, ARIA_HIDDEN, ! visible || null );
  160. setAttribute( slide, TAB_INDEX, visible && options.slideFocus ? 0 : null );
  161. if ( visible ) {
  162. if ( ! hasClass( slide, CLASS_VISIBLE ) ) {
  163. emit( EVENT_VISIBLE, this );
  164. }
  165. } else {
  166. if ( hasClass( slide, CLASS_VISIBLE ) ) {
  167. emit( EVENT_HIDDEN, this );
  168. }
  169. }
  170. }
  171. /**
  172. * Adds a CSS rule to the slider or the container.
  173. *
  174. * @param prop - A property name.
  175. * @param value - A CSS value to add.
  176. * @param useContainer - Optional. Determines whether to apply the rule to the container or not.
  177. */
  178. function rule( prop: string, value: string | number, useContainer?: boolean ): void {
  179. const selector = `#${ slide.id }${ container && useContainer ? ` > .${ CLASS_CONTAINER }` : '' }`;
  180. Components.Style.rule( selector, prop, value );
  181. }
  182. /**
  183. * Checks if the slide is active or not.
  184. *
  185. * @return `true` if the slide is active.
  186. */
  187. function isActive(): boolean {
  188. return Splide.index === index;
  189. }
  190. /**
  191. * Checks if the slide is visible or not.
  192. */
  193. function isVisible(): boolean {
  194. if ( Splide.is( FADE ) ) {
  195. return isActive();
  196. }
  197. const trackRect = rect( Components.Elements.track );
  198. const slideRect = rect( slide );
  199. const left = resolve( 'left' );
  200. const right = resolve( 'right' );
  201. return floor( trackRect[ left ] ) <= slideRect[ left ] && slideRect[ right ] <= ceil( trackRect[ right ] );
  202. }
  203. /**
  204. * Calculates how far this slide is from another slide and
  205. * returns `true` if the distance is within the given number.
  206. *
  207. * @param from - An index of a base slide.
  208. * @param distance - `true` if the slide is within this number.
  209. *
  210. * @return `true` if the slide is within the `distance` from the base slide, or otherwise `false`.
  211. */
  212. function isWithin( from: number, distance: number ): boolean {
  213. let diff = abs( from - index );
  214. if ( ! Splide.is( SLIDE ) && ! isClone ) {
  215. diff = min( diff, Splide.length - diff );
  216. }
  217. return diff <= distance;
  218. }
  219. return {
  220. index,
  221. slideIndex,
  222. slide,
  223. container,
  224. isClone,
  225. mount,
  226. destroy,
  227. rule,
  228. isWithin,
  229. };
  230. }