use-carousel.mjs 7.1 KB

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