Layout.ts 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. import { TTB } from '../../constants/directions';
  2. import { EVENT_OVERFLOW, EVENT_REFRESH, EVENT_RESIZE, EVENT_RESIZED, EVENT_UPDATED } from '../../constants/events';
  3. import { Splide } from '../../core/Splide/Splide';
  4. import { BaseComponent, Components, Options } from '../../types';
  5. import { apply, EventInterface, isObject, rect, style, Throttle, toggleClass, unit } from '@splidejs/utils';
  6. import { assert } from '../../utils';
  7. import { FADE } from '../../constants/types';
  8. import { CLASS_OVERFLOW } from '../../constants/classes';
  9. /**
  10. * The interface for the Layout component.
  11. *
  12. * @since 3.0.0
  13. */
  14. export interface LayoutComponent extends BaseComponent {
  15. listSize(): number;
  16. slideSize( index: number, withoutGap?: boolean ): number;
  17. sliderSize( withoutGap?: boolean ): number;
  18. totalSize( index?: number, withoutGap?: boolean ): number;
  19. getPadding( right: boolean ): number;
  20. isOverflow(): boolean;
  21. /** @internal */
  22. resize( force?: boolean ): void;
  23. }
  24. /**
  25. * The component that adjusts slider styles and provides methods for dimensions.
  26. *
  27. * @since 3.0.0
  28. *
  29. * @param Splide - A Splide instance.
  30. * @param Components - A collection of components.
  31. * @param options - Options.
  32. * @param event - An EventInterface instance.
  33. *
  34. * @return An Layout component object.
  35. */
  36. export function Layout(
  37. Splide: Splide,
  38. Components: Components,
  39. options: Options,
  40. event: EventInterface
  41. ): LayoutComponent {
  42. const { on, bind, emit } = event;
  43. const { Slides } = Components;
  44. const { resolve } = Components.Direction;
  45. const { root, track, list } = Components.Elements;
  46. const { getAt, style: styleSlides } = Slides;
  47. /**
  48. * Indicates whether the slider direction is vertical or not.
  49. */
  50. let vertical: boolean;
  51. /**
  52. * Keeps the DOMRect object of the root element.
  53. */
  54. let rootRect: DOMRect;
  55. /**
  56. * Turns into `true` when the carousel is wider than the list.
  57. */
  58. let overflow: boolean;
  59. /**
  60. * Called when the component is mounted.
  61. */
  62. function mount(): void {
  63. init();
  64. bind( window, 'resize load', Throttle( apply( emit, EVENT_RESIZE ) ) );
  65. on( [ EVENT_UPDATED, EVENT_REFRESH ], init );
  66. on( EVENT_RESIZE, resize );
  67. }
  68. /**
  69. * Initializes the component on `mount` or `updated`.
  70. * Uses `max-width` for the root to prevent the slider from exceeding the parent element.
  71. */
  72. function init(): void {
  73. vertical = options.direction === TTB;
  74. style( root, 'maxWidth', unit( options.width ) );
  75. style( track, resolve( 'paddingLeft' ), cssPadding( false ) );
  76. style( track, resolve( 'paddingRight' ), cssPadding( true ) );
  77. resize( true );
  78. }
  79. /**
  80. * Updates dimensions of some elements when the carousel is resized.
  81. * Also checks the carousel size and emits `overflow` events when it exceeds the list width.
  82. *
  83. * @param force - Skips checking the root dimension change and always performs the resizing process.
  84. */
  85. function resize( force?: boolean ): void {
  86. const newRect = rect( root );
  87. if ( force || rootRect.width !== newRect.width || rootRect.height !== newRect.height ) {
  88. style( track, 'height', cssTrackHeight() );
  89. styleSlides( resolve( 'marginRight' ), unit( options.gap ) );
  90. styleSlides( 'width', cssSlideWidth() );
  91. styleSlides( 'height', cssSlideHeight(), true );
  92. rootRect = newRect;
  93. emit( EVENT_RESIZED );
  94. if ( overflow !== ( overflow = isOverflow() ) ) {
  95. toggleClass( root, CLASS_OVERFLOW, overflow );
  96. emit( EVENT_OVERFLOW, overflow );
  97. }
  98. }
  99. }
  100. /**
  101. * Parses the padding option and returns the value for each side.
  102. * This method returns `paddingTop` or `paddingBottom` for the vertical slider.
  103. *
  104. * @param right - Determines whether to get `paddingRight/Bottom` or `paddingLeft/Top`.
  105. *
  106. * @return The padding value as a CSS string.
  107. */
  108. function cssPadding( right: boolean ): string {
  109. const { padding } = options;
  110. const prop = resolve( right ? 'right' : 'left' );
  111. return padding
  112. && unit( padding[ prop ] || ( isObject( padding ) ? 0 : padding ) )
  113. || '0px';
  114. }
  115. /**
  116. * Returns the height of the track element as a CSS string.
  117. *
  118. * @return The height of the track.
  119. */
  120. function cssTrackHeight(): string {
  121. let height = '';
  122. if ( vertical ) {
  123. height = cssHeight();
  124. assert( height, 'height or heightRatio is missing.' );
  125. height = `calc(${ height } - ${ cssPadding( false ) } - ${ cssPadding( true ) })`;
  126. }
  127. return height;
  128. }
  129. /**
  130. * Converts options related with height to a CSS string.
  131. *
  132. * @return The height as a CSS string if available, or otherwise an empty string.
  133. */
  134. function cssHeight(): string {
  135. return unit( options.height || rect( list ).width * options.heightRatio );
  136. }
  137. /**
  138. * Returns the width of the slide as a CSS string.
  139. *
  140. * @return The width of the slide.
  141. */
  142. function cssSlideWidth(): string | null {
  143. return options.autoWidth
  144. ? null
  145. : unit( options.fixedWidth ) || ( vertical ? '' : cssSlideSize() );
  146. }
  147. /**
  148. * Returns the height of the slide as a CSS string.
  149. *
  150. * @return The height of the slide.
  151. */
  152. function cssSlideHeight(): string | null {
  153. return unit( options.fixedHeight )
  154. || ( vertical ? ( options.autoHeight ? null : cssSlideSize() ) : cssHeight() );
  155. }
  156. /**
  157. * Returns the CSS string for slide width or height without gap.
  158. *
  159. * @return The CSS string for slide width or height.
  160. */
  161. function cssSlideSize(): string {
  162. const gap = unit( options.gap );
  163. return `calc((100%${ gap && ` + ${ gap }` })/${ options.perPage || 1 }${ gap && ` - ${ gap }` })`;
  164. }
  165. /**
  166. * Returns the list width for the horizontal slider, or the height for the vertical slider.
  167. *
  168. * @return The size of the list element in pixel.
  169. */
  170. function listSize(): number {
  171. return rect( list )[ resolve( 'width' ) ];
  172. }
  173. /**
  174. * Returns the slide width for the horizontal slider, or the height for the vertical slider.
  175. *
  176. * @param index - Optional. A slide index.
  177. * @param withoutGap - Optional. Determines whether to exclude the gap amount or not.
  178. *
  179. * @return The size of the specified slide element in pixel.
  180. */
  181. function slideSize( index = 0, withoutGap?: boolean ): number {
  182. const Slide = getAt( index );
  183. return ( Slide ? Slide.size() : 0 ) + ( withoutGap ? 0 : getGap() );
  184. }
  185. /**
  186. * Returns the total width or height of slides from the head of the slider to the specified index.
  187. * This includes sizes of clones before the first slide.
  188. *
  189. * @param index - A slide index. If omitted, uses the last index.
  190. * @param withoutGap - Optional. Determines whether to exclude the last gap or not.
  191. *
  192. * @return The total width of slides in the horizontal slider, or the height in the vertical one.
  193. */
  194. function totalSize( index: number, withoutGap?: boolean ): number {
  195. const Slide = getAt( index );
  196. return Slide ? Slide.pos() + Slide.size() + ( withoutGap ? 0 : getGap() ) : 0;
  197. }
  198. /**
  199. * Returns the slider size without clones before the first slide.
  200. * Do not use the clone's size because it's unstable while initializing and refreshing process.
  201. *
  202. * @param withoutGap - Optional. Determines whether to exclude the last gap or not.
  203. *
  204. * @return The width or height of the slider without clones.
  205. */
  206. function sliderSize( withoutGap?: boolean ): number {
  207. return totalSize( Splide.length - 1 ) - totalSize( 0 ) + slideSize( 0, withoutGap );
  208. }
  209. /**
  210. * Compute the gap by the first and second slides.
  211. * This always returns 0 if the number of slides is less than 2.
  212. *
  213. * @return The gap value in pixel.
  214. */
  215. function getGap(): number {
  216. const first = getAt( 0 );
  217. const second = getAt( 1 );
  218. return first && second ? second.pos() - first.pos() - first.size() : 0;
  219. }
  220. /**
  221. * Returns the padding value.
  222. * This method resolves the difference of the direction.
  223. *
  224. * @param right - Determines whether to get `paddingRight/Bottom` or `paddingLeft/Top`.
  225. *
  226. * @return The padding value in pixel.
  227. */
  228. function getPadding( right: boolean ): number {
  229. return parseFloat( style(
  230. track,
  231. resolve( `padding${ right ? 'Right' : 'Left' }` )
  232. ) ) || 0;
  233. }
  234. /**
  235. * Checks if the carousel is wider than the list.
  236. * This method always returns `true` for a fade carousel.
  237. *
  238. * @return `true` if the carousel is wider than the list, or otherwise `false`.
  239. */
  240. function isOverflow(): boolean {
  241. return Splide.is( FADE ) || sliderSize( true ) > listSize();
  242. }
  243. return {
  244. mount,
  245. resize,
  246. listSize,
  247. slideSize,
  248. sliderSize,
  249. totalSize,
  250. getPadding,
  251. isOverflow,
  252. };
  253. }