foundation.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. import { isObject, get } from 'lodash';
  2. import BaseFoundation, { DefaultAdapter } from '../base/foundation';
  3. import { numbers } from './constants';
  4. export interface CarouselAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
  5. notifyChange: (activeIndex: number, preIndex: number) => void;
  6. setNewActiveIndex: (activeIndex: number) => void;
  7. setPreActiveIndex: (activeIndex: number) => void;
  8. setIsReverse: (isReverse: boolean) => void;
  9. setIsInit: (isInit: boolean) => void
  10. }
  11. class CarouselFoundation<P = Record<string, any>, S = Record<string, any>> extends BaseFoundation<CarouselAdapter<P, S>, P, S> {
  12. constructor(adapter: CarouselAdapter<P, S>) {
  13. super({ ...adapter });
  14. }
  15. _interval = null;
  16. _forcePlay = false;
  17. setForcePlay(forcePlay: boolean) {
  18. this._forcePlay = forcePlay;
  19. }
  20. play(interval: number): void {
  21. if (this._interval) {
  22. clearInterval(this._interval);
  23. }
  24. this._interval = setInterval(() => {
  25. this.next();
  26. }, interval);
  27. }
  28. stop(): void {
  29. if (this._interval) {
  30. clearInterval(this._interval);
  31. }
  32. }
  33. goTo(activeIndex: number): void {
  34. const { activeIndex: stateActiveIndex } = this.getStates();
  35. const targetIndex = this.getValidIndex(activeIndex);
  36. this._adapter.setIsReverse(stateActiveIndex > targetIndex);
  37. if (this.getIsControlledComponent()) {
  38. this._notifyChange(targetIndex);
  39. } else {
  40. this._notifyChange(targetIndex);
  41. this.handleNewActiveIndex(targetIndex);
  42. }
  43. }
  44. next(): void {
  45. this.stop();
  46. const { activeIndex: stateActiveIndex } = this.getStates();
  47. const targetIndex = this.getValidIndex(stateActiveIndex + 1);
  48. this._adapter.setIsReverse(false);
  49. if (this.getIsControlledComponent()) {
  50. this._notifyChange(targetIndex);
  51. } else {
  52. this._notifyChange(targetIndex);
  53. this.handleNewActiveIndex(targetIndex);
  54. }
  55. this.handleAutoPlay();
  56. }
  57. prev(): void {
  58. this.stop();
  59. const { activeIndex: stateActiveIndex } = this.getStates();
  60. const targetIndex = this.getValidIndex(stateActiveIndex - 1);
  61. this._adapter.setIsReverse(true);
  62. if (this.getIsControlledComponent()) {
  63. this._notifyChange(targetIndex);
  64. } else {
  65. this._notifyChange(targetIndex);
  66. this.handleNewActiveIndex(targetIndex);
  67. }
  68. this.handleAutoPlay();
  69. }
  70. destroy(): void {
  71. this._unregisterInterval();
  72. }
  73. _unregisterInterval() {
  74. if (this._interval) {
  75. clearInterval(this._interval);
  76. this._interval = null;
  77. }
  78. }
  79. _notifyChange(activeIndex: number): void {
  80. const { activeIndex: stateActiveIndex, isInit } = this.getStates();
  81. if (isInit) {
  82. this._adapter.setIsInit(false);
  83. }
  84. if (stateActiveIndex !== activeIndex) {
  85. this._adapter.setPreActiveIndex(stateActiveIndex);
  86. this._adapter.notifyChange(activeIndex, stateActiveIndex);
  87. }
  88. }
  89. getValidIndex(index: number): number {
  90. const { children } = this.getStates();
  91. return (index + children.length) % children.length;
  92. }
  93. getSwitchingTime(): number {
  94. const { autoPlay, speed } = this.getProps();
  95. const autoPlayType = typeof autoPlay;
  96. if (autoPlayType === 'boolean') {
  97. return numbers.DEFAULT_INTERVAL + speed;
  98. }
  99. if (isObject(autoPlay)) {
  100. return get(autoPlay, 'interval', numbers.DEFAULT_INTERVAL) + speed;
  101. }
  102. return speed;
  103. }
  104. getIsControlledComponent(): boolean {
  105. return this._isInProps('activeIndex');
  106. }
  107. handleAutoPlay(): void {
  108. const { autoPlay } = this.getProps();
  109. const { children } = this.getStates();
  110. const autoPlayType = typeof autoPlay;
  111. // when user manually call the play function, force play
  112. // only when carousel children length > 1 to start play
  113. if (children.length > 1 && ((autoPlay === true) || isObject(autoPlay) || this._forcePlay)) {
  114. this.play(this.getSwitchingTime());
  115. }
  116. }
  117. handleKeyDown(event: any): void {
  118. if (event.key === 'ArrowLeft') {
  119. this.prev();
  120. }
  121. if (event.key === 'ArrowRight') {
  122. this.next();
  123. }
  124. }
  125. onIndicatorChange(activeIndex: number): void {
  126. const { activeIndex: stateActiveIndex } = this.getStates();
  127. this._adapter.setIsReverse(stateActiveIndex > activeIndex);
  128. this._notifyChange(activeIndex);
  129. if (!this.getIsControlledComponent()) {
  130. this.handleNewActiveIndex(activeIndex);
  131. }
  132. }
  133. handleNewActiveIndex(activeIndex: number): void {
  134. const { activeIndex: stateActiveIndex } = this.getStates();
  135. if (stateActiveIndex !== activeIndex) {
  136. this._adapter.setNewActiveIndex(activeIndex);
  137. }
  138. }
  139. getDefaultActiveIndex(): number {
  140. let activeIndex;
  141. const props = this.getProps();
  142. if ('activeIndex' in props) {
  143. activeIndex = props.activeIndex;
  144. } else if ('defaultActiveIndex' in props) {
  145. activeIndex = props.defaultActiveIndex;
  146. }
  147. return activeIndex;
  148. }
  149. }
  150. export default CarouselFoundation;