Media.ts 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. import { MEDIA_PREFERS_REDUCED_MOTION } from '../../constants/media';
  2. import { CREATED, DESTROYED } from '../../constants/states';
  3. import { EventBinder } from '../../constructors';
  4. import { Splide } from '../../core/Splide/Splide';
  5. import { BaseComponent, Components, Options } from '../../types';
  6. import { merge, omit, ownKeys } from '../../utils';
  7. import { EVENT_UPDATED } from '../../constants/events';
  8. /**
  9. * The interface for the Media component.
  10. *
  11. * @since 4.0.0
  12. */
  13. export interface MediaComponent extends BaseComponent {
  14. /** @internal */
  15. reduce( reduced: boolean ): void;
  16. set( options: Options, base?: boolean, notify?: boolean ): void;
  17. }
  18. /**
  19. * The component for observing media queries and updating options if necessary.
  20. * This used to be the Options component.
  21. *
  22. * @since 4.0.0
  23. *
  24. * @param Splide - A Splide instance.
  25. * @param Components - A collection of components.
  26. * @param options - Options.
  27. *
  28. * @return A Media component object.
  29. */
  30. export function Media( Splide: Splide, Components: Components, options: Options ): MediaComponent {
  31. const { state } = Splide;
  32. const breakpoints = options.breakpoints || {};
  33. const reducedMotion = options.reducedMotion || {};
  34. const binder = EventBinder();
  35. /**
  36. * Stores options and MediaQueryList object.
  37. */
  38. const queries: Array<[ Options, MediaQueryList ]> = [];
  39. /**
  40. * Called when the component is constructed.
  41. */
  42. function setup(): void {
  43. const isMin = options.mediaQuery === 'min';
  44. ownKeys( breakpoints )
  45. .sort( ( n, m ) => isMin ? +n - +m : +m - +n )
  46. .forEach( key => {
  47. register( breakpoints[ key ], `(${ isMin ? 'min' : 'max' }-width:${ key }px)` );
  48. } );
  49. register( reducedMotion, MEDIA_PREFERS_REDUCED_MOTION );
  50. update();
  51. }
  52. /**
  53. * Destroys the component.
  54. *
  55. * @param completely - Will be `true` for complete destruction.
  56. */
  57. function destroy( completely: boolean ): void {
  58. if ( completely ) {
  59. binder.destroy();
  60. }
  61. }
  62. /**
  63. * Registers entries as [ Options, media query string ].
  64. *
  65. * @param options - Options merged to current options when the document matches the query.
  66. * @param query - A query string.
  67. */
  68. function register( options: Options, query: string ): void {
  69. const queryList = matchMedia( query );
  70. binder.bind( queryList, 'change', update );
  71. queries.push( [ options, queryList ] );
  72. }
  73. /**
  74. * Checks all media queries in entries and updates options.
  75. */
  76. function update(): void {
  77. const destroyed = state.is( DESTROYED );
  78. const direction = options.direction;
  79. const merged = queries.reduce<Options>( ( merged, entry ) => {
  80. return merge( merged, entry[ 1 ].matches ? entry[ 0 ] : {} );
  81. }, {} );
  82. omit( options );
  83. set( merged );
  84. if ( options.destroy ) {
  85. Splide.destroy( options.destroy === 'completely' );
  86. } else if ( destroyed ) {
  87. destroy( true );
  88. Splide.mount();
  89. } else {
  90. direction !== options.direction && Splide.refresh();
  91. }
  92. }
  93. /**
  94. * Disables or enables `reducedMotion` options.
  95. * This method does nothing when the document does not match the query.
  96. *
  97. * @internal
  98. *
  99. * @param enable - Determines whether to apply `reducedMotion` options or not.
  100. */
  101. function reduce( enable: boolean ): void {
  102. if ( matchMedia( MEDIA_PREFERS_REDUCED_MOTION ).matches ) {
  103. enable ? merge( options, reducedMotion ) : omit( options, ownKeys( reducedMotion ) );
  104. }
  105. }
  106. /**
  107. * Sets current options or base options (prototype).
  108. * If changing base options, always emits the `updated` event.
  109. *
  110. * @internal
  111. *
  112. * @param opts - New options.
  113. * @param base - Optional. Determines whether to also update base options or not.
  114. * @param notify - Optional. If `true`, always emits the `update` event.
  115. */
  116. function set( opts: Options, base?: boolean, notify?: boolean ): void {
  117. merge( options, opts );
  118. base && merge( Object.getPrototypeOf( options ), opts );
  119. if ( notify || ! state.is( CREATED ) ) {
  120. Splide.emit( EVENT_UPDATED, options );
  121. }
  122. }
  123. return {
  124. setup,
  125. destroy,
  126. reduce,
  127. set,
  128. };
  129. }