pxd 0.0.61 → 0.0.63

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/LICENSE +21 -0
  2. package/dist/components/_internal/dismiss-container.d.vue.ts +28 -0
  3. package/dist/components/_internal/dismiss-container.vue +162 -0
  4. package/dist/components/_internal/popover-arrow.d.vue.ts +9 -0
  5. package/dist/components/_internal/popover-arrow.vue +38 -0
  6. package/dist/components/active-graph/index.vue +4 -4
  7. package/dist/components/avatar/index.vue +5 -7
  8. package/dist/components/avatar-group/index.d.vue.ts +0 -1
  9. package/dist/components/avatar-group/index.vue +1 -1
  10. package/dist/components/backtop/index.vue +1 -1
  11. package/dist/components/badge/index.d.vue.ts +5 -1
  12. package/dist/components/badge/index.vue +18 -4
  13. package/dist/components/badge/types.d.ts +5 -0
  14. package/dist/components/book/index.vue +1 -1
  15. package/dist/components/browser/index.vue +1 -1
  16. package/dist/components/bubble/index.d.vue.ts +22 -0
  17. package/dist/components/bubble/index.vue +59 -0
  18. package/dist/components/bubble/types.d.ts +6 -0
  19. package/dist/components/button/index.d.vue.ts +0 -2
  20. package/dist/components/button/index.vue +30 -21
  21. package/dist/components/button/types.d.ts +3 -2
  22. package/dist/components/button-group/index.d.vue.ts +14 -0
  23. package/dist/components/button-group/index.vue +26 -0
  24. package/dist/components/button-group/types.d.ts +9 -0
  25. package/dist/components/carousel/index.d.vue.ts +3 -3
  26. package/dist/components/carousel/index.vue +146 -113
  27. package/dist/components/carousel/types.d.ts +1 -1
  28. package/dist/components/carousel-item/index.vue +22 -17
  29. package/dist/components/checkbox/index.vue +6 -6
  30. package/dist/components/checkbox-group/index.d.vue.ts +1 -1
  31. package/dist/components/chip/index.d.vue.ts +1 -5
  32. package/dist/components/chip/index.vue +4 -4
  33. package/dist/components/color-selector/index.d.vue.ts +12 -0
  34. package/dist/components/color-selector/index.vue +64 -0
  35. package/dist/components/color-selector/types.d.ts +12 -0
  36. package/dist/components/command-menu/index.d.vue.ts +6 -6
  37. package/dist/components/command-menu/index.vue +23 -32
  38. package/dist/components/command-menu/types.d.ts +1 -1
  39. package/dist/components/command-menu-group/index.vue +15 -6
  40. package/dist/components/command-menu-group/types.d.ts +1 -1
  41. package/dist/components/countdown/index.d.vue.ts +11 -11
  42. package/dist/components/drawer/index.d.vue.ts +8 -8
  43. package/dist/components/drawer/index.vue +13 -10
  44. package/dist/components/drawer/types.d.ts +4 -3
  45. package/dist/components/ellipsis-text/index.d.vue.ts +4 -1
  46. package/dist/components/ellipsis-text/index.vue +84 -107
  47. package/dist/components/ellipsis-text/types.d.ts +2 -1
  48. package/dist/components/error/index.vue +1 -1
  49. package/dist/components/fader/index.vue +5 -9
  50. package/dist/components/gauge/index.vue +34 -29
  51. package/dist/components/grid/index.vue +1 -1
  52. package/dist/components/grid-item/index.vue +1 -1
  53. package/dist/components/hold-button/index.d.vue.ts +8 -10
  54. package/dist/components/hold-button/index.vue +20 -29
  55. package/dist/components/hold-button/types.d.ts +5 -6
  56. package/dist/components/index.d.ts +7 -0
  57. package/dist/components/index.js +7 -0
  58. package/dist/components/input/index.d.vue.ts +8 -8
  59. package/dist/components/input/index.vue +5 -4
  60. package/dist/components/intersection-observer/index.vue +4 -4
  61. package/dist/components/kbd/index.vue +1 -1
  62. package/dist/components/link-button/index.d.vue.ts +4 -4
  63. package/dist/components/link-button/index.vue +9 -8
  64. package/dist/components/link-button/types.d.ts +0 -3
  65. package/dist/components/list/index.d.vue.ts +10 -15
  66. package/dist/components/list/index.vue +58 -131
  67. package/dist/components/list/types.d.ts +4 -4
  68. package/dist/components/list-item/index.d.vue.ts +2 -2
  69. package/dist/components/list-item/index.vue +44 -39
  70. package/dist/components/loading-bar/index.vue +8 -7
  71. package/dist/components/material/index.vue +24 -46
  72. package/dist/components/menu/index.d.vue.ts +6 -8
  73. package/dist/components/menu/index.vue +18 -24
  74. package/dist/components/menu/types.d.ts +1 -2
  75. package/dist/components/message/composables/use-group-expand.d.ts +13 -0
  76. package/dist/components/message/composables/use-group-expand.js +50 -0
  77. package/dist/components/message/composables/use-message-timer.d.ts +9 -0
  78. package/dist/components/message/composables/use-message-timer.js +61 -0
  79. package/dist/components/message/composables/use-promise-message.d.ts +4 -0
  80. package/dist/components/message/composables/use-promise-message.js +49 -0
  81. package/dist/components/message/index.d.vue.ts +6 -33
  82. package/dist/components/message/index.vue +33 -185
  83. package/dist/components/message/types.d.ts +2 -2
  84. package/dist/components/message-item/index.vue +26 -2
  85. package/dist/components/modal/index.d.vue.ts +7 -7
  86. package/dist/components/modal/index.vue +7 -3
  87. package/dist/components/modal/types.d.ts +7 -3
  88. package/dist/components/note/index.vue +2 -2
  89. package/dist/components/number-input/index.d.vue.ts +5 -4
  90. package/dist/components/number-input/index.vue +3 -0
  91. package/dist/components/number-input/types.d.ts +1 -0
  92. package/dist/components/overlay/index.d.vue.ts +6 -3
  93. package/dist/components/overlay/index.vue +63 -68
  94. package/dist/components/overlay/types.d.ts +5 -4
  95. package/dist/components/pagination/index.vue +2 -2
  96. package/dist/components/pin-input/index.d.vue.ts +1 -1
  97. package/dist/components/pin-input/index.vue +7 -6
  98. package/dist/components/placeholder/index.vue +1 -1
  99. package/dist/components/popover/index.d.vue.ts +7 -8
  100. package/dist/components/popover/index.vue +149 -239
  101. package/dist/components/popover/types.d.ts +5 -5
  102. package/dist/components/progress/index.vue +1 -1
  103. package/dist/components/radio/index.vue +2 -2
  104. package/dist/components/resizable/index.vue +43 -51
  105. package/dist/components/resizable/types.d.ts +1 -1
  106. package/dist/components/resizable-handle/index.d.vue.ts +4 -1
  107. package/dist/components/resizable-handle/index.vue +29 -3
  108. package/dist/components/resizable-panel/index.vue +3 -7
  109. package/dist/components/scalable-text/index.d.vue.ts +9 -0
  110. package/dist/components/scalable-text/index.vue +147 -0
  111. package/dist/components/scalable-text/types.d.ts +12 -0
  112. package/dist/components/scrollable/index.d.vue.ts +2 -2
  113. package/dist/components/scrollable/index.vue +4 -3
  114. package/dist/components/separator/index.d.vue.ts +6 -0
  115. package/dist/components/separator/index.vue +18 -0
  116. package/dist/components/separator/types.d.ts +5 -0
  117. package/dist/components/skeleton/index.d.vue.ts +1 -1
  118. package/dist/components/slider/index.d.vue.ts +1 -1
  119. package/dist/components/slider/index.vue +39 -7
  120. package/dist/components/snippet/index.vue +16 -13
  121. package/dist/components/spinner/index.vue +3 -1
  122. package/dist/components/stack/index.d.vue.ts +1 -1
  123. package/dist/components/stack/index.vue +1 -1
  124. package/dist/components/switch/index.d.vue.ts +1 -1
  125. package/dist/components/switch/index.vue +4 -3
  126. package/dist/components/switch-item/index.vue +1 -1
  127. package/dist/components/tabs/index.d.vue.ts +12 -0
  128. package/dist/components/tabs/index.vue +270 -0
  129. package/dist/components/tabs/types.d.ts +12 -0
  130. package/dist/components/tabs-item/index.d.vue.ts +4 -0
  131. package/dist/components/tabs-item/index.vue +16 -0
  132. package/dist/components/tabs-item/types.d.ts +10 -0
  133. package/dist/components/text/index.vue +1 -1
  134. package/dist/components/textarea/index.d.vue.ts +2 -2
  135. package/dist/components/textarea/index.vue +1 -1
  136. package/dist/components/time-picker/index.d.vue.ts +3 -5
  137. package/dist/components/time-picker/index.vue +53 -45
  138. package/dist/components/time-picker/types.d.ts +1 -2
  139. package/dist/components/toggle/index.d.vue.ts +0 -2
  140. package/dist/components/toggle/index.vue +6 -6
  141. package/dist/components/toggle-button/index.vue +8 -6
  142. package/dist/components/tooltip/index.d.vue.ts +1 -1
  143. package/dist/components/tooltip/index.vue +19 -11
  144. package/dist/components/tooltip/types.d.ts +2 -2
  145. package/dist/components/virtual-list/index.d.vue.ts +8 -8
  146. package/dist/components/virtual-list/index.vue +27 -5
  147. package/dist/components/virtual-list/types.d.ts +3 -0
  148. package/dist/composables/index.d.ts +4 -1
  149. package/dist/composables/index.js +4 -1
  150. package/dist/composables/use-browser-observer.js +2 -2
  151. package/dist/composables/use-client-online.js +2 -2
  152. package/dist/composables/use-color-scheme.js +2 -2
  153. package/dist/composables/use-countdown.js +3 -2
  154. package/dist/composables/use-deferred-value.js +2 -2
  155. package/dist/composables/use-delay-destroy.js +11 -6
  156. package/dist/composables/use-document-hidden.js +2 -2
  157. package/dist/composables/use-focus-trap.js +2 -2
  158. package/dist/composables/use-list-filter.d.ts +11 -0
  159. package/dist/composables/use-list-filter.js +56 -0
  160. package/dist/composables/use-list-navigation.d.ts +27 -0
  161. package/dist/composables/use-list-navigation.js +159 -0
  162. package/dist/composables/use-lock-scroll.js +12 -12
  163. package/dist/composables/use-media-query.js +2 -2
  164. package/dist/composables/use-outside-click.d.ts +1 -1
  165. package/dist/composables/use-outside-click.js +8 -11
  166. package/dist/composables/use-overlay-manager.d.ts +18 -0
  167. package/dist/composables/use-overlay-manager.js +80 -0
  168. package/dist/composables/use-popover-responsive.d.ts +6 -8
  169. package/dist/composables/use-popover-responsive.js +9 -12
  170. package/dist/composables/use-repeat-action.js +2 -2
  171. package/dist/composables/use-swipe-gesture.d.ts +65 -0
  172. package/dist/composables/use-swipe-gesture.js +99 -0
  173. package/dist/composables/use-virtual-list.d.ts +5 -3
  174. package/dist/composables/use-virtual-list.js +25 -14
  175. package/dist/composables/use-window-size.js +2 -2
  176. package/dist/constants/size.d.ts +12 -0
  177. package/dist/constants/size.js +12 -0
  178. package/dist/contexts/button.d.ts +5 -0
  179. package/dist/contexts/button.js +5 -0
  180. package/dist/contexts/carousel.d.ts +2 -1
  181. package/dist/contexts/list.d.ts +23 -3
  182. package/dist/contexts/list.js +6 -2
  183. package/dist/contexts/resizable.d.ts +3 -11
  184. package/dist/contexts/tabs.d.ts +15 -0
  185. package/dist/contexts/tabs.js +2 -0
  186. package/dist/locales/en-us.d.ts +4 -4
  187. package/dist/locales/en-us.js +4 -4
  188. package/dist/locales/zh-cn.d.ts +4 -4
  189. package/dist/locales/zh-cn.js +4 -4
  190. package/dist/plugins/dayjs-millisecond-token.js +1 -1
  191. package/dist/styles/source.css +133 -128
  192. package/dist/styles/styles.css +2 -2
  193. package/dist/styles/tw.css +133 -128
  194. package/dist/types/shared/props.d.ts +1 -0
  195. package/dist/types/shared/utils.d.ts +1 -4
  196. package/dist/utils/date.d.ts +3 -3
  197. package/dist/utils/dom.d.ts +1 -0
  198. package/dist/utils/dom.js +4 -0
  199. package/dist/utils/event.d.ts +2 -1
  200. package/dist/utils/event.js +7 -1
  201. package/dist/utils/format.d.ts +3 -3
  202. package/dist/utils/format.js +5 -4
  203. package/dist/utils/fuzzy-search.d.ts +7 -0
  204. package/dist/utils/fuzzy-search.js +61 -0
  205. package/dist/utils/get.d.ts +2 -0
  206. package/dist/utils/get.js +15 -1
  207. package/dist/utils/index.d.ts +10 -11
  208. package/dist/utils/index.js +2 -3
  209. package/dist/utils/ref.d.ts +2 -2
  210. package/dist/utils/{throttle.d.ts → timing.d.ts} +1 -0
  211. package/dist/utils/{throttle.js → timing.js} +4 -2
  212. package/package.json +40 -37
  213. package/volar.d.ts +7 -0
  214. package/dist/components/overlay/overlay-stack.d.ts +0 -3
  215. package/dist/components/overlay/overlay-stack.js +0 -17
  216. package/dist/composables/use-pointer-gesture.d.ts +0 -180
  217. package/dist/composables/use-pointer-gesture.js +0 -406
  218. package/dist/utils/debounce.d.ts +0 -1
  219. package/dist/utils/debounce.js +0 -1
  220. package/dist/utils/regexp.d.ts +0 -8
  221. package/dist/utils/regexp.js +0 -8
  222. package/dist/utils/responsive.d.ts +0 -3
  223. package/dist/utils/responsive.js +0 -14
@@ -1,6 +1,8 @@
1
1
  <script setup>
2
2
  import { tv } from "tailwind-variants";
3
3
  import { computed } from "vue";
4
+ import { BASIC_HEIGHTS } from "../../constants/size";
5
+ import { useButtonGroupContext } from "../../contexts/button";
4
6
  import { useConfigProvider } from "../../contexts/config-provider";
5
7
  import { isTruthyProp } from "../../utils/format";
6
8
  import PSpinner from "../spinner/index.vue";
@@ -10,23 +12,23 @@ defineOptions({
10
12
  });
11
13
  const props = defineProps({
12
14
  as: { type: [String, Object], required: false, default: "button" },
13
- variant: { type: String, required: false, default: "default" },
15
+ variant: { type: String, required: false },
14
16
  size: { type: String, required: false },
15
17
  shape: { type: String, required: false },
16
- align: { type: String, required: false, default: "center" },
18
+ align: { type: String, required: false },
17
19
  icon: { type: Boolean, required: false, default: false },
18
20
  loading: { type: Boolean, required: false },
19
21
  disabled: { type: Boolean, required: false },
20
22
  fullWidth: { type: Boolean, required: false }
21
23
  });
22
24
  const buttonVariants = tv({
23
- base: "pxd-button inline-flex shrink-0 cursor-pointer touch-manipulation items-center font-inherit select-none motion-safe:transition-all",
25
+ base: "pxd-button inline-flex shrink-0 cursor-pointer touch-manipulation items-center font-inherit select-none motion-safe:transition-appearance [[data-button-group]>&]:-ml-px [[data-button-group]>&]:not-first:rounded-l-none [[data-button-group]>&]:not-last:rounded-r-none [[data-button-group]>&]:enabled:hover:z-1",
24
26
  variants: {
25
27
  size: {
26
- xs: "h-6 px-1 text-sm rounded-sm",
27
- sm: "h-7.5 px-1.5 text-sm rounded-md",
28
- md: "h-9 px-2.5 text-sm rounded-md",
29
- lg: "h-10 px-3.5 text-base rounded-lg"
28
+ xs: `${BASIC_HEIGHTS.xs} px-1 text-xs rounded-sm`,
29
+ sm: `${BASIC_HEIGHTS.sm} px-1.5 text-sm rounded-md`,
30
+ md: `${BASIC_HEIGHTS.md} px-2.5 text-sm rounded-md`,
31
+ lg: `${BASIC_HEIGHTS.lg} px-3.5 text-base rounded-lg`
30
32
  },
31
33
  shape: {
32
34
  square: "rounded-none",
@@ -40,9 +42,8 @@ const buttonVariants = tv({
40
42
  },
41
43
  variant: {
42
44
  simple: "",
43
- icon: "p-0! aspect-square",
44
45
  link: "font-medium hover:underline hover:opacity-70 active:opacity-90 motion-safe:transition-opacity",
45
- default: "border-input bg-background-100 text-foreground hover:bg-background-hover active:bg-background-active",
46
+ default: "bg-background-100 text-foreground hover:bg-background-hover active:bg-background-active",
46
47
  ghost: "border-transparent bg-transparent text-foreground hover:bg-background-hover active:bg-background-active",
47
48
  primary: "border-transparent bg-primary text-gray-100 hover:bg-primary-hover active:bg-primary-active",
48
49
  error: "text-white border-transparent bg-red-800 hover:bg-red-700 active:bg-red-900",
@@ -56,7 +57,8 @@ const buttonVariants = tv({
56
57
  true: "w-full"
57
58
  },
58
59
  icon: {
59
- true: "p-0! aspect-square"
60
+ true: "p-0 aspect-square",
61
+ false: "[[data-button-group]>&]:flex-1"
60
62
  },
61
63
  loading: {
62
64
  true: ""
@@ -66,25 +68,23 @@ const buttonVariants = tv({
66
68
  {
67
69
  variant: ["default", "ghost", "primary", "error", "warning", "success"],
68
70
  class: "border self-focus-ring outline-none"
69
- },
70
- {
71
- icon: true,
72
- class: "p-0! aspect-square"
73
71
  }
74
72
  ]
75
73
  });
76
74
  const configProvider = useConfigProvider();
75
+ const buttonGroupContext = useButtonGroupContext();
77
76
  const isDisabled = computed(
78
- () => isTruthyProp(props.disabled) || isTruthyProp(props.loading)
77
+ () => isTruthyProp(props.disabled) || isTruthyProp(props.loading) || buttonGroupContext?.props.disabled || false
79
78
  );
80
79
  const computedClasses = computed(() => {
81
- const { size = configProvider.size, shape, align, variant, fullWidth, icon } = props;
80
+ const { size, shape, align, variant, fullWidth, icon } = props;
81
+ const internalAlign = icon ? "center" : align;
82
82
  return buttonVariants({
83
83
  icon,
84
- size,
85
- shape,
86
- align,
87
- variant,
84
+ size: size || buttonGroupContext?.props.size || configProvider.size,
85
+ shape: buttonGroupContext ? "default" : shape,
86
+ align: internalAlign || buttonGroupContext?.props.align || "center",
87
+ variant: variant || buttonGroupContext?.props.variant || "default",
88
88
  fullWidth,
89
89
  disabled: isDisabled.value
90
90
  });
@@ -92,7 +92,16 @@ const computedClasses = computed(() => {
92
92
  </script>
93
93
 
94
94
  <template>
95
- <Component :is="as" role="button" :class="computedClasses" :disabled="isDisabled" v-bind="$attrs">
95
+ <Component
96
+ :is="as"
97
+ tabindex="0"
98
+ aria-label="Action"
99
+ :aria-busy="loading"
100
+ :aria-disabled="isDisabled"
101
+ :class="computedClasses"
102
+ :disabled="isDisabled"
103
+ v-bind="$attrs"
104
+ >
96
105
  <PSpinner v-if="loading" />
97
106
 
98
107
  <slot name="prefix" />
@@ -1,18 +1,19 @@
1
1
  import type {
2
2
  ComponentAs,
3
+ ComponentAlign,
3
4
  ComponentShape,
4
5
  ComponentSizeWithXs,
5
6
  ComponentVariantWithDefault,
6
7
  } from '../../types/shared'
7
8
 
8
- export type ButtonVariant = ComponentVariantWithDefault | 'ghost' | 'simple' | 'icon' | 'link'
9
+ export type ButtonVariant = ComponentVariantWithDefault | 'ghost' | 'simple' | 'link'
9
10
 
10
11
  export interface ButtonProps {
11
12
  as?: ComponentAs
12
13
  variant?: ButtonVariant
13
14
  size?: ComponentSizeWithXs
14
15
  shape?: ComponentShape
15
- align?: 'left' | 'center' | 'right'
16
+ align?: ComponentAlign
16
17
  icon?: boolean
17
18
  loading?: boolean
18
19
  disabled?: boolean
@@ -0,0 +1,14 @@
1
+ import type { ButtonGroupProps } from './types';
2
+ declare var __VLS_1: {};
3
+ type __VLS_Slots = {} & {
4
+ default?: (props: typeof __VLS_1) => any;
5
+ };
6
+ declare const __VLS_base: import("vue").DefineComponent<ButtonGroupProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<ButtonGroupProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
7
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
8
+ declare const _default: typeof __VLS_export;
9
+ export default _default;
10
+ type __VLS_WithSlots<T, S> = T & {
11
+ new (): {
12
+ $slots: S;
13
+ };
14
+ };
@@ -0,0 +1,26 @@
1
+ <script setup>
2
+ import { provideButtonGroupContext } from "../../contexts/button";
3
+ defineOptions({
4
+ name: "PButtonGroup",
5
+ inheritAttrs: false
6
+ });
7
+ const props = defineProps({
8
+ size: { type: String, required: false },
9
+ align: { type: String, required: false },
10
+ variant: { type: String, required: false },
11
+ disabled: { type: Boolean, required: false }
12
+ });
13
+ provideButtonGroupContext({ props });
14
+ </script>
15
+
16
+ <template>
17
+ <div
18
+ role="group"
19
+ aria-label="Actions"
20
+ data-button-group
21
+ class="pxd-button-group flex max-w-full"
22
+ v-bind="$attrs"
23
+ >
24
+ <slot />
25
+ </div>
26
+ </template>
@@ -0,0 +1,9 @@
1
+ import type { ComponentSizeWithXs, ComponentAlign } from '../../types/shared'
2
+ import type { ButtonVariant } from '../button/types'
3
+
4
+ export interface ButtonGroupProps {
5
+ size?: ComponentSizeWithXs
6
+ align?: ComponentAlign
7
+ variant?: ButtonVariant
8
+ disabled?: boolean
9
+ }
@@ -13,16 +13,16 @@ declare const __VLS_base: import("vue").DefineComponent<CarouselProps, {}, {}, {
13
13
  }, string, import("vue").PublicProps, Readonly<CarouselProps> & Readonly<{
14
14
  onChange?: ((index: number) => any) | undefined;
15
15
  }>, {
16
- index: number;
16
+ direction: import("../../types/shared").ComponentDirection;
17
17
  height: number | string;
18
+ index: number;
18
19
  loop: boolean;
19
20
  arrow: boolean;
20
21
  autoplay: boolean;
21
22
  interval: number;
22
23
  indicator: boolean;
23
- direction: import("../../types/shared").ComponentDirection;
24
24
  indicatorType: "dot" | "line";
25
- indicatorPosition: import("../../types/shared").BasePosition;
25
+ indicatorPosition: import("../../types/shared").BasePosition | "center";
26
26
  pauseOnHover: boolean;
27
27
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
28
28
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
@@ -1,7 +1,9 @@
1
1
  <script setup>
2
2
  import ChevronRightIcon from "@gdsicon/vue/chevron-right";
3
3
  import { computed, nextTick, onBeforeUnmount, onMounted, ref, shallowRef } from "vue";
4
+ import { useSwipeGesture } from "../../composables/use-swipe-gesture";
4
5
  import { provideCarouselContext } from "../../contexts/carousel";
6
+ import { awaitAnimationEnd } from "../../utils/dom";
5
7
  import { getCssUnitValue } from "../../utils/format";
6
8
  defineOptions({
7
9
  name: "PCarousel",
@@ -16,67 +18,100 @@ const props = defineProps({
16
18
  interval: { type: Number, required: false, default: 3e3 },
17
19
  indicator: { type: Boolean, required: false, default: true },
18
20
  direction: { type: String, required: false, default: "horizontal" },
19
- indicatorType: { type: String, required: false, default: "line" },
20
- indicatorPosition: { type: String, required: false, default: "bottom" },
21
+ indicatorType: { type: String, required: false, default: "dot" },
22
+ indicatorPosition: { type: String, required: false, default: "center" },
21
23
  pauseOnHover: { type: Boolean, required: false, default: true },
22
24
  toggleOnWheel: { type: Boolean, required: false }
23
25
  });
24
26
  const emits = defineEmits(["change"]);
25
- const TRANSITION_CLASSES = ["transition-transform", "duration-[calc(var(--duration,200ms)+100ms)]"];
26
- let autoPlayRafId = null;
27
- let autoPlaySession = 0;
27
+ let isToggling = false;
28
+ let pendingDelta = null;
29
+ let autoPlayTimerId = null;
28
30
  let isPointerEntering = false;
29
- let toggleQueue = Promise.resolve();
31
+ let maxDrag = 0;
30
32
  const carousels = ref([]);
31
33
  const sliderRef = shallowRef();
32
34
  const virtualIndex = shallowRef(props.index);
33
- const correctIndex = computed(() => {
34
- const index = virtualIndex.value;
35
- if (index >= carousels.value.length) {
35
+ const gestureMoveOffset = shallowRef(0);
36
+ const displayIndex = computed(() => {
37
+ const length = carousels.value.length;
38
+ if (length === 0) {
36
39
  return 0;
37
40
  }
38
- if (index <= -1) {
39
- return carousels.value.length - 1;
40
- }
41
- return index;
41
+ return (virtualIndex.value % length + length) % length;
42
42
  });
43
+ const isAtFirst = computed(() => displayIndex.value === 0);
44
+ const isAtLast = computed(() => displayIndex.value === carousels.value.length - 1);
45
+ const isHorizontal = computed(() => props.direction === "horizontal");
43
46
  const computedStyle = computed(() => {
44
- const translateValue = virtualIndex.value * -100;
45
- const styles = {
46
- transform: props.direction === "horizontal" ? `translateX(${translateValue}%)` : `translateY(${translateValue}%)`
47
+ const translateValue = `calc(${virtualIndex.value * -100}% + ${gestureMoveOffset.value}px)`;
48
+ return {
49
+ transform: `translate${isHorizontal.value ? "X" : "Y"}(${translateValue})`
47
50
  };
48
- return styles;
49
51
  });
50
- function translateItems() {
51
- carousels.value.forEach((carousel, index) => {
52
- carousel.translateItem(index, virtualIndex.value);
53
- });
54
- }
55
- async function awaitAnimationEnd() {
56
- const animations = sliderRef.value?.getAnimations() ?? [];
57
- await Promise.allSettled(animations.map((a) => a.finished));
58
- }
52
+ useSwipeGesture(sliderRef, {
53
+ direction: () => props.direction,
54
+ onPress: ({ size }) => {
55
+ maxDrag = size;
56
+ gestureMoveOffset.value = 0;
57
+ onPointerEnter();
58
+ },
59
+ onFollow: (ev) => {
60
+ if (!props.loop && (isAtFirst.value && ev.delta > 0 || isAtLast.value && ev.delta < 0)) {
61
+ return;
62
+ }
63
+ gestureMoveOffset.value = Math.max(-maxDrag, Math.min(maxDrag, ev.displacement));
64
+ },
65
+ onRelease: ({ swiped, direction }) => {
66
+ gestureMoveOffset.value = 0;
67
+ onPointerLeave();
68
+ if (!swiped || !direction) {
69
+ return;
70
+ }
71
+ if (isHorizontal.value) {
72
+ performToggle(direction === "left" ? 1 : -1);
73
+ } else {
74
+ performToggle(direction === "top" ? 1 : -1);
75
+ }
76
+ }
77
+ });
59
78
  async function performToggle(delta) {
60
79
  const length = carousels.value.length;
61
80
  if (length === 0) {
62
81
  return;
63
82
  }
64
- await awaitAnimationEnd();
83
+ await awaitAnimationEnd(sliderRef.value);
65
84
  if (props.loop) {
66
85
  virtualIndex.value += delta;
67
- translateItems();
86
+ await nextTick();
87
+ await awaitAnimationEnd(sliderRef.value);
88
+ if (virtualIndex.value >= length) {
89
+ await resetSliderPosition(0);
90
+ } else if (virtualIndex.value <= -1) {
91
+ await resetSliderPosition(length - 1);
92
+ }
68
93
  } else {
69
94
  virtualIndex.value = Math.max(0, Math.min(virtualIndex.value + delta, length - 1));
70
95
  }
71
- emits("change", correctIndex.value);
72
- nextTick(onPointerLeave);
96
+ emits("change", virtualIndex.value);
97
+ restartAutoPlay();
73
98
  }
74
- function onToggleClick(delta) {
75
- toggleQueue = toggleQueue.catch(() => {
76
- }).then(async () => {
99
+ async function onToggleClick(delta) {
100
+ if (isToggling) {
101
+ pendingDelta = delta;
102
+ return;
103
+ }
104
+ isToggling = true;
105
+ try {
77
106
  await performToggle(delta);
78
- });
79
- return toggleQueue;
107
+ while (pendingDelta != null) {
108
+ const next = pendingDelta;
109
+ pendingDelta = null;
110
+ await performToggle(next);
111
+ }
112
+ } finally {
113
+ isToggling = false;
114
+ }
80
115
  }
81
116
  function onWheelToggle(ev) {
82
117
  if (!props.toggleOnWheel) {
@@ -87,11 +122,9 @@ function onWheelToggle(ev) {
87
122
  return;
88
123
  }
89
124
  const delta = ev.deltaY > 0 ? 1 : -1;
90
- const indexBefore = virtualIndex.value;
91
- const lastIndex = length - 1;
92
125
  if (!props.loop) {
93
- const isAtFirstAndGoPrev = indexBefore <= 0 && delta < 0;
94
- const isAtLastAndGoNext = indexBefore >= lastIndex && delta > 0;
126
+ const isAtFirstAndGoPrev = isAtFirst.value && delta < 0;
127
+ const isAtLastAndGoNext = isAtLast.value && delta > 0;
95
128
  if (isAtFirstAndGoPrev || isAtLastAndGoNext) {
96
129
  return;
97
130
  }
@@ -101,51 +134,31 @@ function onWheelToggle(ev) {
101
134
  }
102
135
  onToggleClick(delta);
103
136
  }
104
- function resetContainerPosition(resetIndex) {
105
- const containerClassList = sliderRef.value.classList;
106
- containerClassList.remove(...TRANSITION_CLASSES);
107
- virtualIndex.value = resetIndex;
108
- translateItems();
109
- setTimeout(() => {
110
- containerClassList.add(...TRANSITION_CLASSES);
111
- }, 0);
112
- }
113
- function onTransitionsEnd(ev) {
114
- if (ev.propertyName !== "transform" || ev.target !== sliderRef.value) {
137
+ async function resetSliderPosition(resetIndex) {
138
+ const el = sliderRef.value;
139
+ if (!el) {
115
140
  return;
116
141
  }
117
- if (!props.loop) {
118
- return;
119
- }
120
- if (virtualIndex.value >= carousels.value.length) {
121
- resetContainerPosition(0);
122
- } else if (virtualIndex.value <= -1) {
123
- resetContainerPosition(carousels.value.length - 1);
124
- }
142
+ el.style.transition = "none";
143
+ virtualIndex.value = resetIndex;
144
+ await nextTick();
145
+ void el.offsetHeight;
146
+ el.style.transition = "";
125
147
  }
126
148
  function clearAutoPlayTimer() {
127
- autoPlaySession++;
128
- if (autoPlayRafId != null) {
129
- cancelAnimationFrame(autoPlayRafId);
130
- autoPlayRafId = null;
149
+ if (autoPlayTimerId != null) {
150
+ clearTimeout(autoPlayTimerId);
151
+ autoPlayTimerId = null;
131
152
  }
132
153
  }
133
- function setAutoPlayTimer() {
134
- const mySession = autoPlaySession;
135
- const startTime = performance.now();
136
- const onAnimationFrame = () => {
137
- if (mySession !== autoPlaySession || isPointerEntering) {
138
- return;
139
- }
140
- const currentTime = performance.now();
141
- const elapsedTime = currentTime - startTime;
142
- if (elapsedTime >= props.interval) {
143
- onToggleClick(1);
144
- return;
145
- }
146
- autoPlayRafId = requestAnimationFrame(onAnimationFrame);
147
- };
148
- autoPlayRafId = requestAnimationFrame(onAnimationFrame);
154
+ function restartAutoPlay() {
155
+ clearAutoPlayTimer();
156
+ if (!props.autoplay || isPointerEntering) {
157
+ return;
158
+ }
159
+ autoPlayTimerId = setTimeout(() => {
160
+ onToggleClick(1);
161
+ }, props.interval);
149
162
  }
150
163
  function onPointerEnter() {
151
164
  if (props.pauseOnHover) {
@@ -155,41 +168,43 @@ function onPointerEnter() {
155
168
  }
156
169
  function onPointerLeave() {
157
170
  isPointerEntering = false;
158
- if (!props.autoplay) {
159
- return;
160
- }
161
- clearAutoPlayTimer();
162
- setAutoPlayTimer();
171
+ restartAutoPlay();
163
172
  }
164
173
  function onIndicatorClick(ev) {
165
- clearAutoPlayTimer();
166
174
  const targetEl = ev.target.closest("[data-index]");
167
175
  const targetIndex = Number(targetEl?.dataset.index);
168
176
  if (Number.isNaN(targetIndex)) {
169
177
  return;
170
178
  }
171
- const deltaIndex = targetIndex - virtualIndex.value;
179
+ const deltaIndex = targetIndex - displayIndex.value;
172
180
  if (deltaIndex !== 0) {
181
+ clearAutoPlayTimer();
173
182
  onToggleClick(deltaIndex);
174
183
  }
175
- nextTick(onPointerLeave);
184
+ }
185
+ function updateItemIndex() {
186
+ carousels.value.forEach((carousel, i) => {
187
+ carousel.updateItemIndex(i);
188
+ });
176
189
  }
177
190
  function registerCarousel(state) {
178
191
  carousels.value.push(state);
192
+ updateItemIndex();
179
193
  }
180
194
  function unregisterCarousel(id) {
181
195
  carousels.value = carousels.value.filter(({ uid }) => uid !== id);
196
+ updateItemIndex();
182
197
  }
183
198
  provideCarouselContext({
184
199
  props,
185
200
  carousels,
201
+ virtualIndex,
186
202
  registerCarousel,
187
203
  unregisterCarousel
188
204
  });
189
205
  onMounted(async () => {
190
- onPointerLeave();
191
206
  await nextTick();
192
- translateItems();
207
+ restartAutoPlay();
193
208
  });
194
209
  onBeforeUnmount(() => {
195
210
  clearAutoPlayTimer();
@@ -200,7 +215,7 @@ onBeforeUnmount(() => {
200
215
  <template>
201
216
  <div
202
217
  v-bind="$attrs"
203
- :data-direction="direction"
218
+ :data-orientation="direction"
204
219
  :data-indicator-type="indicatorType"
205
220
  :data-indicator-position="indicatorPosition"
206
221
  class="pxd-carousel group relative w-full touch-none overflow-hidden"
@@ -209,13 +224,11 @@ onBeforeUnmount(() => {
209
224
  @pointerleave="onPointerLeave"
210
225
  @wheel="onWheelToggle"
211
226
  >
212
- <div class="pxd-carousel--container size-full">
227
+ <div class="pxd-carousel--container size-full overflow-clip">
213
228
  <div
214
229
  ref="sliderRef"
215
- class="pxd-carousel--slider translate-z-0 size-full"
230
+ class="pxd-carousel--slider translate-z-0 size-full active:transition-none motion-safe:transition-transform"
216
231
  :style="computedStyle"
217
- :class="TRANSITION_CLASSES"
218
- @transitionend="onTransitionsEnd"
219
232
  >
220
233
  <slot />
221
234
  </div>
@@ -223,37 +236,39 @@ onBeforeUnmount(() => {
223
236
 
224
237
  <div
225
238
  v-if="indicator"
226
- class="pxd-carousel--indicator gap-2 absolute flex w-max items-center"
239
+ class="pxd-carousel--indicator gap-2 absolute z-1 flex w-max items-center"
227
240
  @click="onIndicatorClick"
228
241
  >
229
- <slot name="indicator" :current="correctIndex + 1" :total="carousels.length">
242
+ <slot name="indicator" :current="displayIndex" :total="carousels.length">
230
243
  <button
231
244
  v-for="(_, i) in carousels.length"
232
245
  :key="i"
233
246
  :data-index="i"
234
247
  class="pxd-carousel--indicator-item relative h-(--carousel-dot-height) w-(--carousel-dot-width) cursor-pointer appearance-none rounded-full bg-gray-alpha-200 font-inherit self-focus-ring outline-none hover:bg-gray-alpha-400 motion-safe:transition-colors"
235
- :class="{ 'bg-primary!': i === correctIndex }"
248
+ :class="{ 'bg-primary!': i === displayIndex }"
236
249
  />
237
250
  </slot>
238
251
  </div>
239
252
 
240
- <div v-if="arrow" class="pxd-carousel--toggler gap-2 absolute flex">
253
+ <div v-if="arrow" class="pxd-carousel--toggler gap-2 pointer-events-none absolute z-1 flex">
241
254
  <button
242
255
  type="button"
243
256
  aria-label="Carousel arrow left"
244
- class="pxd-carousel--prev-btn p-1.5 cursor-pointer appearance-none rounded-md bg-gray-alpha-100 font-inherit text-foreground-secondary self-focus-ring outline-none hover:bg-background-hover active:bg-background-active disabled:pointer-events-none motion-safe:transition-colors"
257
+ :disabled="!loop && isAtFirst"
258
+ class="pxd-carousel--prev-btn p-1.5 pointer-events-auto cursor-pointer appearance-none rounded-md bg-gray-alpha-100 font-inherit text-foreground-secondary self-focus-ring outline-none enabled:hover:bg-background-hover enabled:active:bg-background-active disabled:cursor-not-allowed disabled:opacity-50 motion-safe:transition-colors"
245
259
  @click="onToggleClick(-1)"
246
260
  >
247
- <ChevronRightIcon class="rotate-180" />
261
+ <ChevronRightIcon class="pointer-events-none rotate-180" />
248
262
  </button>
249
263
 
250
264
  <button
251
265
  type="button"
252
266
  aria-label="Carousel arrow right"
253
- class="pxd-carousel--next-btn p-1.5 cursor-pointer appearance-none rounded-md bg-gray-alpha-100 font-inherit text-foreground-secondary self-focus-ring outline-none hover:bg-background-hover active:bg-background-active disabled:pointer-events-none motion-safe:transition-colors"
267
+ :disabled="!loop && isAtLast"
268
+ class="pxd-carousel--next-btn p-1.5 pointer-events-auto cursor-pointer appearance-none rounded-md bg-gray-alpha-100 font-inherit text-foreground-secondary self-focus-ring outline-none enabled:hover:bg-background-hover enabled:active:bg-background-active disabled:cursor-not-allowed disabled:opacity-50 motion-safe:transition-colors"
254
269
  @click="onToggleClick(1)"
255
270
  >
256
- <ChevronRightIcon />
271
+ <ChevronRightIcon class="pointer-events-none" />
257
272
  </button>
258
273
  </div>
259
274
  </div>
@@ -268,7 +283,8 @@ onBeforeUnmount(() => {
268
283
 
269
284
  &[data-indicator-type='line'] {
270
285
  &[data-indicator-position='top'],
271
- &[data-indicator-position='bottom'] {
286
+ &[data-indicator-position='bottom'],
287
+ &[data-indicator-position='center'] {
272
288
  --carousel-dot-width: 1rem;
273
289
  --carousel-dot-height: 0.25rem;
274
290
  }
@@ -282,7 +298,7 @@ onBeforeUnmount(() => {
282
298
 
283
299
  &[data-indicator-position='top'] {
284
300
  .pxd-carousel--indicator {
285
- left: 12px;
301
+ left: 0.75rem;
286
302
  top: 0.5rem;
287
303
  }
288
304
 
@@ -294,7 +310,7 @@ onBeforeUnmount(() => {
294
310
 
295
311
  &[data-indicator-position='bottom'] {
296
312
  .pxd-carousel--indicator {
297
- left: 12px;
313
+ left: 0.75rem;
298
314
  bottom: 0.5rem;
299
315
  }
300
316
 
@@ -307,7 +323,7 @@ onBeforeUnmount(() => {
307
323
  &[data-indicator-position='left'] {
308
324
  .pxd-carousel--indicator {
309
325
  left: 0.5rem;
310
- top: 12px;
326
+ top: 0.75rem;
311
327
  }
312
328
 
313
329
  .pxd-carousel--toggler {
@@ -319,7 +335,7 @@ onBeforeUnmount(() => {
319
335
  &[data-indicator-position='right'] {
320
336
  .pxd-carousel--indicator {
321
337
  right: 0.5rem;
322
- top: 12px;
338
+ top: 0.75rem;
323
339
  }
324
340
 
325
341
  .pxd-carousel--toggler {
@@ -328,14 +344,20 @@ onBeforeUnmount(() => {
328
344
  }
329
345
  }
330
346
 
331
- &[data-direction='horizontal'] .pxd-carousel--slider {
332
- display: flex;
333
- }
347
+ &[data-indicator-position='center'] {
348
+ .pxd-carousel--indicator {
349
+ left: 50%;
350
+ bottom: 0.5rem;
351
+ transform: translateX(-50%);
352
+ }
334
353
 
335
- &[data-direction='vertical'] {
336
- .pxd-carousel--prev-btn,
337
- .pxd-carousel--next-btn {
338
- transform: rotate(90deg);
354
+ .pxd-carousel--toggler {
355
+ left: 0;
356
+ top: 50%;
357
+ width: 100%;
358
+ padding-inline: 1rem;
359
+ justify-content: space-between;
360
+ transform: translateY(-50%);
339
361
  }
340
362
  }
341
363
 
@@ -346,6 +368,17 @@ onBeforeUnmount(() => {
346
368
  flex-direction: column;
347
369
  }
348
370
  }
371
+
372
+ &[data-orientation='horizontal'] .pxd-carousel--slider {
373
+ display: flex;
374
+ }
375
+
376
+ &[data-orientation='vertical'] {
377
+ .pxd-carousel--prev-btn,
378
+ .pxd-carousel--next-btn {
379
+ transform: rotate(90deg);
380
+ }
381
+ }
349
382
  }
350
383
 
351
384
  .pxd-carousel--indicator-item::before {
@@ -10,7 +10,7 @@ export interface CarouselProps {
10
10
  indicator?: boolean
11
11
  direction?: ComponentDirection
12
12
  indicatorType?: 'dot' | 'line'
13
- indicatorPosition?: BasePosition
13
+ indicatorPosition?: BasePosition | 'center'
14
14
  pauseOnHover?: boolean
15
15
  toggleOnWheel?: boolean
16
16
  }