use-carousel.mjs 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. import { getCurrentInstance, useSlots, ref, computed, unref, isVNode, watch, shallowRef, onMounted, onBeforeUnmount, provide } from 'vue';
  2. import { throttle } from 'lodash-unified';
  3. import { useResizeObserver } from '@vueuse/core';
  4. import { CAROUSEL_ITEM_NAME, carouselContextKey } from './constants.mjs';
  5. import { useOrderedChildren } from '../../../hooks/use-ordered-children/index.mjs';
  6. import { isString } from '@vue/shared';
  7. import { debugWarn } from '../../../utils/error.mjs';
  8. import { flattedChildren } from '../../../utils/vue/vnode.mjs';
  9. import { CHANGE_EVENT } from '../../../constants/event.mjs';
  10. const THROTTLE_TIME = 300;
  11. const useCarousel = (props, emit, componentName) => {
  12. const {
  13. children: items,
  14. addChild: addItem,
  15. removeChild: removeItem
  16. } = useOrderedChildren(getCurrentInstance(), CAROUSEL_ITEM_NAME);
  17. const slots = useSlots();
  18. const activeIndex = ref(-1);
  19. const timer = ref(null);
  20. const hover = ref(false);
  21. const root = ref();
  22. const containerHeight = ref(0);
  23. const isItemsTwoLength = ref(true);
  24. const isFirstCall = ref(true);
  25. const isTransitioning = ref(false);
  26. const arrowDisplay = computed(() => props.arrow !== "never" && !unref(isVertical));
  27. const hasLabel = computed(() => {
  28. return items.value.some((item) => item.props.label.toString().length > 0);
  29. });
  30. const isCardType = computed(() => props.type === "card");
  31. const isVertical = computed(() => props.direction === "vertical");
  32. const containerStyle = computed(() => {
  33. if (props.height !== "auto") {
  34. return {
  35. height: props.height
  36. };
  37. }
  38. return {
  39. height: `${containerHeight.value}px`,
  40. overflow: "hidden"
  41. };
  42. });
  43. const throttledArrowClick = throttle((index) => {
  44. setActiveItem(index);
  45. }, THROTTLE_TIME, { trailing: true });
  46. const throttledIndicatorHover = throttle((index) => {
  47. handleIndicatorHover(index);
  48. }, THROTTLE_TIME);
  49. const isTwoLengthShow = (index) => {
  50. if (!isItemsTwoLength.value)
  51. return true;
  52. return activeIndex.value <= 1 ? index <= 1 : index > 1;
  53. };
  54. function pauseTimer() {
  55. if (timer.value) {
  56. clearInterval(timer.value);
  57. timer.value = null;
  58. }
  59. }
  60. function startTimer() {
  61. if (props.interval <= 0 || !props.autoplay || timer.value)
  62. return;
  63. timer.value = setInterval(() => playSlides(), props.interval);
  64. }
  65. const playSlides = () => {
  66. if (!isFirstCall.value) {
  67. isTransitioning.value = true;
  68. }
  69. isFirstCall.value = false;
  70. if (activeIndex.value < items.value.length - 1) {
  71. activeIndex.value = activeIndex.value + 1;
  72. } else if (props.loop) {
  73. activeIndex.value = 0;
  74. } else {
  75. isTransitioning.value = false;
  76. }
  77. };
  78. function setActiveItem(index) {
  79. if (!isFirstCall.value) {
  80. isTransitioning.value = true;
  81. }
  82. isFirstCall.value = false;
  83. if (isString(index)) {
  84. const filteredItems = items.value.filter((item) => item.props.name === index);
  85. if (filteredItems.length > 0) {
  86. index = items.value.indexOf(filteredItems[0]);
  87. }
  88. }
  89. index = Number(index);
  90. if (Number.isNaN(index) || index !== Math.floor(index)) {
  91. debugWarn(componentName, "index must be integer.");
  92. return;
  93. }
  94. const itemCount = items.value.length;
  95. const oldIndex = activeIndex.value;
  96. if (index < 0) {
  97. activeIndex.value = props.loop ? itemCount - 1 : 0;
  98. } else if (index >= itemCount) {
  99. activeIndex.value = props.loop ? 0 : itemCount - 1;
  100. } else {
  101. activeIndex.value = index;
  102. }
  103. if (oldIndex === activeIndex.value) {
  104. resetItemPosition(oldIndex);
  105. }
  106. resetTimer();
  107. }
  108. function resetItemPosition(oldIndex) {
  109. items.value.forEach((item, index) => {
  110. item.translateItem(index, activeIndex.value, oldIndex);
  111. });
  112. }
  113. function itemInStage(item, index) {
  114. var _a, _b, _c, _d;
  115. const _items = unref(items);
  116. const itemCount = _items.length;
  117. if (itemCount === 0 || !item.states.inStage)
  118. return false;
  119. const nextItemIndex = index + 1;
  120. const prevItemIndex = index - 1;
  121. const lastItemIndex = itemCount - 1;
  122. const isLastItemActive = _items[lastItemIndex].states.active;
  123. const isFirstItemActive = _items[0].states.active;
  124. const isNextItemActive = (_b = (_a = _items[nextItemIndex]) == null ? void 0 : _a.states) == null ? void 0 : _b.active;
  125. const isPrevItemActive = (_d = (_c = _items[prevItemIndex]) == null ? void 0 : _c.states) == null ? void 0 : _d.active;
  126. if (index === lastItemIndex && isFirstItemActive || isNextItemActive) {
  127. return "left";
  128. } else if (index === 0 && isLastItemActive || isPrevItemActive) {
  129. return "right";
  130. }
  131. return false;
  132. }
  133. function handleMouseEnter() {
  134. hover.value = true;
  135. if (props.pauseOnHover) {
  136. pauseTimer();
  137. }
  138. }
  139. function handleMouseLeave() {
  140. hover.value = false;
  141. startTimer();
  142. }
  143. function handleTransitionEnd() {
  144. isTransitioning.value = false;
  145. }
  146. function handleButtonEnter(arrow) {
  147. if (unref(isVertical))
  148. return;
  149. items.value.forEach((item, index) => {
  150. if (arrow === itemInStage(item, index)) {
  151. item.states.hover = true;
  152. }
  153. });
  154. }
  155. function handleButtonLeave() {
  156. if (unref(isVertical))
  157. return;
  158. items.value.forEach((item) => {
  159. item.states.hover = false;
  160. });
  161. }
  162. function handleIndicatorClick(index) {
  163. if (index !== activeIndex.value) {
  164. if (!isFirstCall.value) {
  165. isTransitioning.value = true;
  166. }
  167. }
  168. activeIndex.value = index;
  169. }
  170. function handleIndicatorHover(index) {
  171. if (props.trigger === "hover" && index !== activeIndex.value) {
  172. activeIndex.value = index;
  173. if (!isFirstCall.value) {
  174. isTransitioning.value = true;
  175. }
  176. }
  177. }
  178. function prev() {
  179. setActiveItem(activeIndex.value - 1);
  180. }
  181. function next() {
  182. setActiveItem(activeIndex.value + 1);
  183. }
  184. function resetTimer() {
  185. pauseTimer();
  186. if (!props.pauseOnHover)
  187. startTimer();
  188. }
  189. function setContainerHeight(height) {
  190. if (props.height !== "auto")
  191. return;
  192. containerHeight.value = height;
  193. }
  194. function PlaceholderItem() {
  195. var _a;
  196. const defaultSlots = (_a = slots.default) == null ? void 0 : _a.call(slots);
  197. if (!defaultSlots)
  198. return null;
  199. const flatSlots = flattedChildren(defaultSlots);
  200. const normalizeSlots = flatSlots.filter((slot) => {
  201. return isVNode(slot) && slot.type.name === CAROUSEL_ITEM_NAME;
  202. });
  203. if ((normalizeSlots == null ? void 0 : normalizeSlots.length) === 2 && props.loop && !isCardType.value) {
  204. isItemsTwoLength.value = true;
  205. return normalizeSlots;
  206. }
  207. isItemsTwoLength.value = false;
  208. return null;
  209. }
  210. watch(() => activeIndex.value, (current, prev2) => {
  211. resetItemPosition(prev2);
  212. if (isItemsTwoLength.value) {
  213. current = current % 2;
  214. prev2 = prev2 % 2;
  215. }
  216. if (prev2 > -1) {
  217. emit(CHANGE_EVENT, current, prev2);
  218. }
  219. });
  220. watch(() => props.autoplay, (autoplay) => {
  221. autoplay ? startTimer() : pauseTimer();
  222. });
  223. watch(() => props.loop, () => {
  224. setActiveItem(activeIndex.value);
  225. });
  226. watch(() => props.interval, () => {
  227. resetTimer();
  228. });
  229. const resizeObserver = shallowRef();
  230. onMounted(() => {
  231. watch(() => items.value, () => {
  232. if (items.value.length > 0)
  233. setActiveItem(props.initialIndex);
  234. }, {
  235. immediate: true
  236. });
  237. resizeObserver.value = useResizeObserver(root.value, () => {
  238. resetItemPosition();
  239. });
  240. startTimer();
  241. });
  242. onBeforeUnmount(() => {
  243. pauseTimer();
  244. if (root.value && resizeObserver.value)
  245. resizeObserver.value.stop();
  246. });
  247. provide(carouselContextKey, {
  248. root,
  249. isCardType,
  250. isVertical,
  251. items,
  252. loop: props.loop,
  253. cardScale: props.cardScale,
  254. addItem,
  255. removeItem,
  256. setActiveItem,
  257. setContainerHeight
  258. });
  259. return {
  260. root,
  261. activeIndex,
  262. arrowDisplay,
  263. hasLabel,
  264. hover,
  265. isCardType,
  266. isTransitioning,
  267. items,
  268. isVertical,
  269. containerStyle,
  270. isItemsTwoLength,
  271. handleButtonEnter,
  272. handleTransitionEnd,
  273. handleButtonLeave,
  274. handleIndicatorClick,
  275. handleMouseEnter,
  276. handleMouseLeave,
  277. setActiveItem,
  278. prev,
  279. next,
  280. PlaceholderItem,
  281. isTwoLengthShow,
  282. throttledArrowClick,
  283. throttledIndicatorHover
  284. };
  285. };
  286. export { useCarousel };
  287. //# sourceMappingURL=use-carousel.mjs.map