tide-design-system 2.5.0 → 2.5.3

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 (43) hide show
  1. package/.storybook/main.ts +2 -0
  2. package/README.md +3 -1
  3. package/dist/css/reset.css +5 -1
  4. package/dist/css/utilities-base.css +6 -6
  5. package/dist/css/utilities-responsive.css +24 -24
  6. package/dist/css/variables.css +3 -0
  7. package/dist/style.css +1 -1
  8. package/dist/tide-design-system.cjs +2 -2
  9. package/dist/tide-design-system.esm.d.ts +51 -9
  10. package/dist/tide-design-system.esm.js +1621 -1481
  11. package/dist/utilities/validation.ts +1 -1
  12. package/docs/assets/full-bleed.gif +0 -0
  13. package/docs/assets/layout-grid-default.webp +0 -0
  14. package/docs/assets/layout-grid-fluid.webp +0 -0
  15. package/docs/assets/layout-grid.webp +0 -0
  16. package/docs/configuation.md +47 -0
  17. package/docs/grid-layout.md +83 -0
  18. package/index.ts +4 -0
  19. package/package.json +1 -1
  20. package/src/assets/css/reset.css +5 -1
  21. package/src/assets/css/utilities-base.css +6 -6
  22. package/src/assets/css/utilities-responsive.css +24 -24
  23. package/src/assets/css/variables.css +3 -0
  24. package/src/components/TideAlert.vue +1 -1
  25. package/src/components/TideCarousel.vue +104 -40
  26. package/src/components/TideInputSelect.vue +1 -1
  27. package/src/components/TideInputSelectDeprecated.vue +1 -1
  28. package/src/components/TideInputText.vue +2 -2
  29. package/src/components/TideInputTextDeprecated.vue +2 -2
  30. package/src/components/TideInputTextarea.vue +2 -2
  31. package/src/components/TideInputTextareaDeprecated.vue +2 -2
  32. package/src/components/TideLink.vue +6 -0
  33. package/src/components/TideMenuItem.vue +1 -1
  34. package/src/components/TideModal.vue +1 -1
  35. package/src/components/TideRating.vue +93 -0
  36. package/src/components/TideSheet.vue +1 -1
  37. package/src/components/TideTabs.vue +58 -0
  38. package/src/stories/TideCarousel.stories.ts +47 -25
  39. package/src/stories/TideRating.stories.ts +120 -0
  40. package/src/stories/TideTabs.stories.ts +115 -0
  41. package/src/types/Formatted.ts +1 -1
  42. package/src/utilities/validation.ts +1 -1
  43. package/tests/utilities-format.spec.ts +40 -0
@@ -62,7 +62,7 @@ export const getFieldValidationResult = ({
62
62
 
63
63
  // custom validator prop errors from have second highest precedence
64
64
  if (validators) {
65
- const validation = validateProperty(value.value, validators);
65
+ const validation = validateProperty(value.value ?? '', validators);
66
66
 
67
67
  if (!validation.valid) {
68
68
  return validation;
Binary file
Binary file
@@ -0,0 +1,47 @@
1
+ # TIDE Configuration
2
+
3
+ You can configure global TIDE behavior using `provideTideConfig()`. Place it in a top-level file such as `app.vue` (anywhere Vue’s `provide()` works). See Vue’s docs on [provide/inject](https://vuejs.org/guide/components/provide-inject) for more details.
4
+
5
+ ## `provideTideConfig()`
6
+
7
+ ``` ts
8
+ import { provideTideConfig } from 'tide-design-system';
9
+
10
+ provideTideConfig({
11
+ linkComponent: 'a', // default
12
+ });
13
+ ```
14
+
15
+ ### Config values
16
+
17
+ | Key | Type | Default | Description |
18
+ | --- | --- | --- | --- |
19
+ | `linkComponent` | `'a' \| Component` | `'a'` | Replaces the root element used by link-based TIDE components. |
20
+
21
+ ## `linkComponent` (TIDE 2.5+)
22
+
23
+ Use this to replace all `<a>` elements in TIDE components with a custom component. Useful for frameworks like Nuxt.
24
+
25
+ ### Nuxt Example (using `NuxtLink`)
26
+
27
+ ``` vue
28
+ <!-- app.vue -->
29
+ <script setup lang="ts">
30
+ import { provideTideConfig } from 'tide-design-system';
31
+ import { NuxtLink } from '#components';
32
+
33
+ provideTideConfig({
34
+ linkComponent: NuxtLink,
35
+ });
36
+ </script>
37
+ ```
38
+
39
+ This updates all TIDE components that render links, including:
40
+
41
+ - TideLink
42
+ - TideButton (`:element="ELEMENT.LINK"`)
43
+ - TideCard (`:type="TYPE_CARD.ACTION"` with `href`)
44
+ - TideBreadCrumbs
45
+ - And others
46
+
47
+ All these will now render using `<NuxtLink />` automatically.
@@ -0,0 +1,83 @@
1
+ # TIDE Layout Grid Utility
2
+
3
+ TIDE provides a lightweight CSS Grid for consistent page alignment, gutters, and max-width, with minimal markup. It separates **layout** (page width, gutters) from **component** concerns (spacing, internal layout).
4
+
5
+ <img src="./assets/layout-grid.webp" width="937" height="420" /><br>
6
+
7
+ ## Core Concepts
8
+
9
+ ### Three-Column Grid
10
+
11
+ ```
12
+ | gutter | content | gutter |
13
+ ```
14
+
15
+ Direct children are stacked in the **center content column** by default.
16
+
17
+ ### Responsive Max Width
18
+
19
+ | Variant | Max content width | Class |
20
+ | ------- | ----------------- | ------------- |
21
+ | Default | 1232px | – |
22
+ | XL | 1920px | `CSS.GRID.XL` |
23
+
24
+ ``` vue
25
+ <div :class="[CSS.GRID.LAYOUT, CSS.GRID.XL]">...</div>
26
+ ```
27
+
28
+ ### Responsive Gutters
29
+
30
+ | Breakpoint | Min gutter |
31
+ | ---------- | ------------------ |
32
+ | XS | `--tide-spacing-1` |
33
+ | XS - SM | `--tide-spacing-2` |
34
+ | SM - MD | `--tide-spacing-4` |
35
+
36
+ ---
37
+
38
+ ## Usage
39
+
40
+ ### Page Layout Context
41
+
42
+ ``` vue
43
+ <div :class="[CSS.GRID.LAYOUT]">
44
+ <section><ArticleIntro /></section>
45
+ <section><ArticleBody /></section>
46
+ </div>
47
+ ```
48
+
49
+ Direct children are **centered automatically**, like so:
50
+
51
+ <img src="./assets/layout-grid-default.webp" width="937" height="420" /><br>
52
+
53
+ ### Full-Width / Edge-to-Edge Sections
54
+
55
+ Use `CSS.GRID.FLUID` to break out of the content column:
56
+
57
+ ``` vue
58
+ <div :class="[CSS.GRID.LAYOUT]">
59
+ <section><ArticleIntro /></section>
60
+
61
+ <section :class="[CSS.GRID.FLUID]">
62
+ <HeroBanner />
63
+ </section>
64
+
65
+ <section><ArticleBody /></section>
66
+ </div>
67
+ ```
68
+
69
+ The code in this example would result in a layout like this:
70
+
71
+ <img src="./assets/layout-grid-fluid.webp" width="937" height="420" /><br>
72
+
73
+ Notice the `<HeroBanner />` is no longer confined to the center column, and instead breaks out and stretches **edge-to-edge**. This section is now considered **fluid**.
74
+
75
+ Common use cases for fluid sections:
76
+
77
+ - Header/footer
78
+ - Full-bleed carousels
79
+ - Edge-to-edge hero sections
80
+ - Sticky bars
81
+ - Page sections with toned backgrounds
82
+
83
+ <img src="./assets/full-bleed.gif" width="127" height="277" /><br>
package/index.ts CHANGED
@@ -35,9 +35,11 @@ import TideMenuItem from '@/components/TideMenuItem.vue';
35
35
  import TideModal from '@/components/TideModal.vue';
36
36
  import TidePagination from '@/components/TidePagination.vue';
37
37
  import TidePopover from '@/components/TidePopover.vue';
38
+ import TideRating from '@/components/TideRating.vue';
38
39
  import TideSeoLinks from '@/components/TideSeoLinks.vue';
39
40
  import TideSheet from '@/components/TideSheet.vue';
40
41
  import TideSwitch from '@/components/TideSwitch.vue';
42
+ import TideTabs from '@/components/TideTabs.vue';
41
43
  import { provideTideConfig } from '@/composables/useTideConfig';
42
44
  import { useTideForm } from '@/composables/useTideForm';
43
45
  import { ALERT } from '@/types/Alert';
@@ -182,7 +184,9 @@ export {
182
184
  TideModal,
183
185
  TidePagination,
184
186
  TidePopover,
187
+ TideRating,
185
188
  TideSeoLinks,
186
189
  TideSheet,
187
190
  TideSwitch,
191
+ TideTabs,
188
192
  };
package/package.json CHANGED
@@ -63,7 +63,7 @@
63
63
  "main": "dist/tide-design-system.cjs",
64
64
  "module": "dist/tide-design-system.esm.js",
65
65
  "types": "dist/tide-design-system.esm.d.ts",
66
- "version": "2.5.0",
66
+ "version": "2.5.3",
67
67
  "dependencies": {
68
68
  "@floating-ui/vue": "^1.1.6"
69
69
  }
@@ -7,6 +7,7 @@ body {
7
7
  font-size: var(--tide-font-16);
8
8
  font-weight: 400;
9
9
  line-height: 1.4;
10
+ text-wrap: pretty;
10
11
  }
11
12
 
12
13
  /* Cannot be applied to body tag in marketplace repo due to data-css-scope */
@@ -39,13 +40,16 @@ h2 {font-size: var(--tide-font-24);} /* 24px */
39
40
  h3 {font-size: var(--tide-font-20);} /* 20px */
40
41
  h4 {font-size: var(--tide-font-16);} /* 16px */
41
42
  h5 {font-size: var(--tide-font-16);} /* 16px */
43
+ h6 {font-size: var(--tide-font-16);} /* 16px */
42
44
 
43
45
  h1,
44
46
  h2,
45
47
  h3,
46
48
  h4,
47
- h5 {
49
+ h5,
50
+ h6 {
48
51
  font-weight: 700;
52
+ text-wrap: balance;
49
53
  }
50
54
 
51
55
  img,
@@ -294,12 +294,12 @@
294
294
  .tide-transparent-400 {background-color: var(--tide-transparent-400);}
295
295
 
296
296
  /* Typographic roles */
297
- .tide-typography-display-1 {font-size: var(--tide-font-32); font-weight: 700;}
298
- .tide-typography-headline-1 {font-size: var(--tide-font-24); font-weight: 700;}
299
- .tide-typography-headline-2 {font-size: var(--tide-font-20); font-weight: 700;}
300
- .tide-typography-headline-3 {font-size: var(--tide-font-16); font-weight: 700;}
301
- .tide-typography-title-1 {font-size: var(--tide-font-20); font-weight: 600;}
302
- .tide-typography-title-2 {font-size: var(--tide-font-18); font-weight: 600;}
297
+ .tide-typography-display-1 {font-size: var(--tide-font-32); font-weight: 700; text-wrap: balance;}
298
+ .tide-typography-headline-1 {font-size: var(--tide-font-24); font-weight: 700; text-wrap: balance;}
299
+ .tide-typography-headline-2 {font-size: var(--tide-font-20); font-weight: 700; text-wrap: balance;}
300
+ .tide-typography-headline-3 {font-size: var(--tide-font-16); font-weight: 700; text-wrap: balance;}
301
+ .tide-typography-title-1 {font-size: var(--tide-font-20); font-weight: 600; text-wrap: balance;}
302
+ .tide-typography-title-2 {font-size: var(--tide-font-18); font-weight: 600; text-wrap: balance;}
303
303
  .tide-typography-body-1 {font-size: var(--tide-font-16); font-weight: 400;}
304
304
  .tide-typography-body-2 {font-size: var(--tide-font-14); font-weight: 400;}
305
305
  .tide-typography-label-1 {font-size: var(--tide-font-16); font-weight: 500;}
@@ -295,12 +295,12 @@
295
295
  .sm-tide-transparent-400 {background-color: var(--tide-transparent-400);}
296
296
 
297
297
  /* Typographic roles */
298
- .sm-tide-typography-display-1 {font-size: var(--tide-font-32); font-weight: 700;}
299
- .sm-tide-typography-headline-1 {font-size: var(--tide-font-24); font-weight: 700;}
300
- .sm-tide-typography-headline-2 {font-size: var(--tide-font-20); font-weight: 700;}
301
- .sm-tide-typography-headline-3 {font-size: var(--tide-font-16); font-weight: 700;}
302
- .sm-tide-typography-title-1 {font-size: var(--tide-font-20); font-weight: 600;}
303
- .sm-tide-typography-title-2 {font-size: var(--tide-font-18); font-weight: 600;}
298
+ .sm-tide-typography-display-1 {font-size: var(--tide-font-32); font-weight: 700; text-wrap: balance;}
299
+ .sm-tide-typography-headline-1 {font-size: var(--tide-font-24); font-weight: 700; text-wrap: balance;}
300
+ .sm-tide-typography-headline-2 {font-size: var(--tide-font-20); font-weight: 700; text-wrap: balance;}
301
+ .sm-tide-typography-headline-3 {font-size: var(--tide-font-16); font-weight: 700; text-wrap: balance;}
302
+ .sm-tide-typography-title-1 {font-size: var(--tide-font-20); font-weight: 600; text-wrap: balance;}
303
+ .sm-tide-typography-title-2 {font-size: var(--tide-font-18); font-weight: 600; text-wrap: balance;}
304
304
  .sm-tide-typography-body-1 {font-size: var(--tide-font-16); font-weight: 400;}
305
305
  .sm-tide-typography-body-2 {font-size: var(--tide-font-14); font-weight: 400;}
306
306
  .sm-tide-typography-label-1 {font-size: var(--tide-font-16); font-weight: 500;}
@@ -843,12 +843,12 @@
843
843
  .md-tide-transparent-400 {background-color: var(--tide-transparent-400);}
844
844
 
845
845
  /* Typographic roles */
846
- .md-tide-typography-display-1 {font-size: var(--tide-font-32); font-weight: 700;}
847
- .md-tide-typography-headline-1 {font-size: var(--tide-font-24); font-weight: 700;}
848
- .md-tide-typography-headline-2 {font-size: var(--tide-font-20); font-weight: 700;}
849
- .md-tide-typography-headline-3 {font-size: var(--tide-font-16); font-weight: 700;}
850
- .md-tide-typography-title-1 {font-size: var(--tide-font-20); font-weight: 600;}
851
- .md-tide-typography-title-2 {font-size: var(--tide-font-18); font-weight: 600;}
846
+ .md-tide-typography-display-1 {font-size: var(--tide-font-32); font-weight: 700; text-wrap: balance;}
847
+ .md-tide-typography-headline-1 {font-size: var(--tide-font-24); font-weight: 700; text-wrap: balance;}
848
+ .md-tide-typography-headline-2 {font-size: var(--tide-font-20); font-weight: 700; text-wrap: balance;}
849
+ .md-tide-typography-headline-3 {font-size: var(--tide-font-16); font-weight: 700; text-wrap: balance;}
850
+ .md-tide-typography-title-1 {font-size: var(--tide-font-20); font-weight: 600; text-wrap: balance;}
851
+ .md-tide-typography-title-2 {font-size: var(--tide-font-18); font-weight: 600; text-wrap: balance;}
852
852
  .md-tide-typography-body-1 {font-size: var(--tide-font-16); font-weight: 400;}
853
853
  .md-tide-typography-body-2 {font-size: var(--tide-font-14); font-weight: 400;}
854
854
  .md-tide-typography-label-1 {font-size: var(--tide-font-16); font-weight: 500;}
@@ -1391,12 +1391,12 @@
1391
1391
  .lg-tide-transparent-400 {background-color: var(--tide-transparent-400);}
1392
1392
 
1393
1393
  /* Typographic roles */
1394
- .lg-tide-typography-display-1 {font-size: var(--tide-font-32); font-weight: 700;}
1395
- .lg-tide-typography-headline-1 {font-size: var(--tide-font-24); font-weight: 700;}
1396
- .lg-tide-typography-headline-2 {font-size: var(--tide-font-20); font-weight: 700;}
1397
- .lg-tide-typography-headline-3 {font-size: var(--tide-font-16); font-weight: 700;}
1398
- .lg-tide-typography-title-1 {font-size: var(--tide-font-20); font-weight: 600;}
1399
- .lg-tide-typography-title-2 {font-size: var(--tide-font-18); font-weight: 600;}
1394
+ .lg-tide-typography-display-1 {font-size: var(--tide-font-32); font-weight: 700; text-wrap: balance;}
1395
+ .lg-tide-typography-headline-1 {font-size: var(--tide-font-24); font-weight: 700; text-wrap: balance;}
1396
+ .lg-tide-typography-headline-2 {font-size: var(--tide-font-20); font-weight: 700; text-wrap: balance;}
1397
+ .lg-tide-typography-headline-3 {font-size: var(--tide-font-16); font-weight: 700; text-wrap: balance;}
1398
+ .lg-tide-typography-title-1 {font-size: var(--tide-font-20); font-weight: 600; text-wrap: balance;}
1399
+ .lg-tide-typography-title-2 {font-size: var(--tide-font-18); font-weight: 600; text-wrap: balance;}
1400
1400
  .lg-tide-typography-body-1 {font-size: var(--tide-font-16); font-weight: 400;}
1401
1401
  .lg-tide-typography-body-2 {font-size: var(--tide-font-14); font-weight: 400;}
1402
1402
  .lg-tide-typography-label-1 {font-size: var(--tide-font-16); font-weight: 500;}
@@ -1939,12 +1939,12 @@
1939
1939
  .xl-tide-transparent-400 {background-color: var(--tide-transparent-400);}
1940
1940
 
1941
1941
  /* Typographic roles */
1942
- .xl-tide-typography-display-1 {font-size: var(--tide-font-32); font-weight: 700;}
1943
- .xl-tide-typography-headline-1 {font-size: var(--tide-font-24); font-weight: 700;}
1944
- .xl-tide-typography-headline-2 {font-size: var(--tide-font-20); font-weight: 700;}
1945
- .xl-tide-typography-headline-3 {font-size: var(--tide-font-16); font-weight: 700;}
1946
- .xl-tide-typography-title-1 {font-size: var(--tide-font-20); font-weight: 600;}
1947
- .xl-tide-typography-title-2 {font-size: var(--tide-font-18); font-weight: 600;}
1942
+ .xl-tide-typography-display-1 {font-size: var(--tide-font-32); font-weight: 700; text-wrap: balance;}
1943
+ .xl-tide-typography-headline-1 {font-size: var(--tide-font-24); font-weight: 700; text-wrap: balance;}
1944
+ .xl-tide-typography-headline-2 {font-size: var(--tide-font-20); font-weight: 700; text-wrap: balance;}
1945
+ .xl-tide-typography-headline-3 {font-size: var(--tide-font-16); font-weight: 700; text-wrap: balance;}
1946
+ .xl-tide-typography-title-1 {font-size: var(--tide-font-20); font-weight: 600; text-wrap: balance;}
1947
+ .xl-tide-typography-title-2 {font-size: var(--tide-font-18); font-weight: 600; text-wrap: balance;}
1948
1948
  .xl-tide-typography-body-1 {font-size: var(--tide-font-16); font-weight: 400;}
1949
1949
  .xl-tide-typography-body-2 {font-size: var(--tide-font-14); font-weight: 400;}
1950
1950
  .xl-tide-typography-label-1 {font-size: var(--tide-font-16); font-weight: 500;}
@@ -38,6 +38,9 @@
38
38
  --tide-black: var(--tide-gray-900);
39
39
  --tide-white: var(--tide-gray-100);
40
40
 
41
+ /* CSS Grid */
42
+ --tide-safe-fr: minmax(0, 1fr);
43
+
41
44
  /* Opacity */
42
45
  --tide-disabled: 0.333;
43
46
 
@@ -145,7 +145,7 @@
145
145
  grid-template-areas:
146
146
  'icon heading close'
147
147
  '. body .';
148
- grid-template-columns: auto 1fr auto;
148
+ grid-template-columns: auto var(--tide-safe-fr) auto;
149
149
  grid-column-gap: 0.5rem;
150
150
  max-width: 40rem;
151
151
  }
@@ -34,6 +34,7 @@
34
34
 
35
35
  const emit = defineEmits<Emits>();
36
36
 
37
+ const carouselRef = ref<HTMLDivElement | null>(null);
37
38
  const containerRef = ref<HTMLDivElement | null>(null);
38
39
  const contentWidth = ref<number>(0);
39
40
  const currentPageIndex = ref<number>(0);
@@ -46,26 +47,25 @@
46
47
  const slides = ref<HTMLElement[]>([]);
47
48
  const slidesInView = ref<number[]>([]);
48
49
  const slotObserver = ref<MutationObserver | null>(null);
50
+ const touchStart = ref<Touch | undefined>(undefined);
51
+ const slidesInViewCount = ref<number>(1);
49
52
 
50
53
  const currentPage = computed(() => currentPageIndex.value + 1);
51
54
  const dotContainerWidth = computed(() => dotCountVisible.value * dotWidth + (dotCountVisible.value - 1) * dotGap);
52
55
  const dotCountVisible = computed(() => (props.maxDots > totalPages.value ? totalPages.value : props.maxDots));
53
- const lastPageIndex = computed(() => totalPages.value - 1);
54
56
  const totalPages = computed(() => {
55
57
  if (!slides.value.length) return 0;
56
58
  if (!props.isScrollByPage) return slides.value.length;
57
59
 
58
- const gapWidth = cardGap * (slides.value.length - 1);
59
- const contentNoGap = contentWidth.value - gapWidth;
60
- const quotient = Math.round(contentNoGap / frameWidth.value);
61
- const remainder = contentNoGap % frameWidth.value;
60
+ const quotient = Math.floor(slides.value.length / slidesInViewCount.value);
61
+ const remainder = slides.value.length % slidesInViewCount.value;
62
62
 
63
63
  return remainder ? quotient + 1 : quotient;
64
64
  });
65
65
 
66
- const cardGap: number = 16;
67
66
  const dotGap: number = 8;
68
67
  const dotWidth: number = 8;
68
+ let scrollTimeout: number | undefined;
69
69
 
70
70
  const getDotClass = (dotIndex: number) => {
71
71
  let className = '';
@@ -76,17 +76,64 @@
76
76
  return className;
77
77
  };
78
78
 
79
+ const getIsElementWithinContainer = (element: HTMLElement, container: HTMLElement) => {
80
+ const containerRect = container.getBoundingClientRect();
81
+ const containerRight = containerRect.left + containerRect.width;
82
+ const elementRect = element.getBoundingClientRect();
83
+ const elementRight = elementRect.left + elementRect.width;
84
+
85
+ return elementRight <= containerRight && elementRect.left >= containerRect.left;
86
+ };
87
+
88
+ const handleTouchEnd = (event: TouchEvent) => {
89
+ if (!touchStart.value) return;
90
+
91
+ const touchEnd = event.changedTouches[0];
92
+ const deltaX = touchStart.value.clientX - touchEnd.clientX;
93
+ const deltaY = touchEnd.clientY - touchStart.value.clientY;
94
+
95
+ if (Math.abs(deltaX) > Math.abs(deltaY)) scrollByDelta(deltaX);
96
+ };
97
+
98
+ const handleTouchStart = (event: TouchEvent) => {
99
+ touchStart.value = event.touches[0];
100
+ };
101
+
102
+ const handleWheel = (event: WheelEvent) => {
103
+ const isShiftKeyDown = event.shiftKey;
104
+ const isWheel = Math.abs(event.deltaY) >= 80;
105
+
106
+ if (isWheel && !isShiftKeyDown) return;
107
+ if (event.shiftKey) event.preventDefault();
108
+
109
+ clearTimeout(scrollTimeout);
110
+
111
+ scrollTimeout = window.setTimeout(() => {
112
+ if (isWheel) {
113
+ scrollByDelta(event.deltaY);
114
+ } else {
115
+ const offset = slides.value[slidesInView.value[0]]?.offsetLeft || 0;
116
+
117
+ scrollToOffset(offset);
118
+ }
119
+ }, 100);
120
+ };
121
+
79
122
  const measureDom = () => {
80
123
  if (!containerRef.value) return;
81
124
 
82
125
  contentWidth.value = containerRef.value.scrollWidth;
83
126
  frameWidth.value = containerRef.value.clientWidth;
84
127
  showButtons.value = contentWidth.value > frameWidth.value;
128
+
129
+ slidesInViewCount.value = slides.value.filter((slide) =>
130
+ getIsElementWithinContainer(slide, containerRef.value as HTMLElement)
131
+ ).length;
85
132
  };
86
133
 
87
134
  const observeSlides = () => {
88
135
  const options = {
89
- root: containerRef?.value,
136
+ root: carouselRef?.value,
90
137
  rootMargin: '0px 1px 0px 0px',
91
138
  threshold: 1,
92
139
  };
@@ -131,12 +178,20 @@
131
178
  if (containerRef.value) slotObserver.value.observe(containerRef.value, { childList: true });
132
179
  };
133
180
 
181
+ const scrollByDelta = (delta: number) => {
182
+ const isScrollingLeft = delta < 0;
183
+
184
+ if (isScrollingLeft) {
185
+ props.isScrollByPage ? showPreviousPage() : showPreviousSlide();
186
+ } else {
187
+ props.isScrollByPage ? showNextPage() : showNextSlide();
188
+ }
189
+ };
190
+
134
191
  const scrollToOffset = (target: number) => {
135
192
  if (containerRef.value === null) return;
136
193
 
137
- const lastOffset: number = slides.value[slides.value.length - 1].offsetLeft;
138
- const placement = (target / lastOffset) * lastPageIndex.value;
139
- const isScrollingLeft = placement <= currentPageIndex.value;
194
+ const isScrollingLeft = target <= containerRef.value.scrollLeft;
140
195
 
141
196
  currentPageIndex.value = isScrollingLeft ? currentPageIndex.value - 1 : currentPageIndex.value + 1;
142
197
 
@@ -148,19 +203,21 @@
148
203
  };
149
204
 
150
205
  const showNextPage = () => {
151
- if (slidesInView.value.length === 0) return;
206
+ if (slidesInView.value.length === 0 || isLastSlide.value) return;
152
207
 
153
208
  const nextSlide: number = slidesInView.value[slidesInView.value.length - 1] + 1;
209
+ const offset = slides.value[nextSlide]?.offsetLeft;
154
210
 
155
- scrollToOffset(slides.value[nextSlide].offsetLeft);
211
+ scrollToOffset(offset);
156
212
  };
157
213
 
158
214
  const showPreviousPage = () => {
159
- if (slidesInView.value.length === 0) return;
215
+ if (slidesInView.value.length === 0 || isFirstSlide.value) return;
160
216
 
161
217
  const previousSlide: number = slidesInView.value[0] - slidesInView.value.length;
218
+ const offset = slides.value[previousSlide]?.offsetLeft || 0;
162
219
 
163
- scrollToOffset(slides.value[previousSlide]?.offsetLeft || 0);
220
+ scrollToOffset(offset);
164
221
  };
165
222
 
166
223
  const showNextSlide = () => {
@@ -284,51 +341,58 @@
284
341
  CSS.SNAP.ON,
285
342
  ]"
286
343
  ref="containerRef"
344
+ @touchend="handleTouchEnd"
345
+ @touchstart.passive="handleTouchStart"
346
+ @wheel="handleWheel"
287
347
  >
288
348
  <slot />
289
349
  </ul>
290
350
 
291
351
  <div
292
352
  :class="[
293
- 'tide-carousel-dots-container',
294
353
  CSS.POSITION.ABSOLUTE,
295
354
  CSS.POSITIONING.BOTTOM,
296
355
  CSS.DISPLAY.FLEX,
297
356
  CSS.AXIS1.CENTER,
298
357
  CSS.MARGIN.BOTTOM.HALF,
299
358
  CSS.WIDTH.FULL,
359
+ CSS.POINTER_EVENTS.OFF,
300
360
  ]"
301
- :style="{
302
- width: `${dotContainerWidth}px`,
303
- }"
304
- v-if="isScrollByPage && hasDots && totalPages > 1"
361
+ v-if="hasDots && totalPages > 1"
305
362
  >
306
363
  <div
307
- :class="[
308
- 'tide-carousel-dots',
309
- CSS.DISPLAY.FLEX,
310
- CSS.AXIS1.START,
311
- CSS.AXIS2.CENTER,
312
- CSS.GAP.HALF,
313
- CSS.OVERFLOW.X.SCROLL,
314
- CSS.POINTER_EVENTS.OFF,
315
- CSS.SCROLLBAR.OFF,
316
- ]"
317
- ref="dotsRef"
364
+ :class="['tide-carousel-dots-container']"
365
+ :style="{
366
+ width: `${dotContainerWidth}px`,
367
+ }"
318
368
  >
319
369
  <div
320
370
  :class="[
321
- 'tide-carousel-dot',
322
- CSS.FLEX.SHRINK.OFF,
323
- CSS.HEIGHT.ZERO,
324
- CSS.WIDTH.ZERO,
325
- CSS.BORDER.RADIUS.FULL,
326
- CSS.BG.SURFACE.DEFAULT,
327
- getDotClass(index),
371
+ 'tide-carousel-dots',
372
+ CSS.DISPLAY.FLEX,
373
+ CSS.AXIS1.START,
374
+ CSS.AXIS2.CENTER,
375
+ CSS.GAP.HALF,
376
+ CSS.OVERFLOW.X.SCROLL,
377
+ CSS.POINTER_EVENTS.OFF,
378
+ CSS.SCROLLBAR.OFF,
328
379
  ]"
329
- :key="index"
330
- v-for="(_, index) in totalPages"
331
- />
380
+ ref="dotsRef"
381
+ >
382
+ <div
383
+ :class="[
384
+ 'tide-carousel-dot',
385
+ CSS.FLEX.SHRINK.OFF,
386
+ CSS.HEIGHT.ZERO,
387
+ CSS.WIDTH.ZERO,
388
+ CSS.BORDER.RADIUS.FULL,
389
+ CSS.BG.SURFACE.DEFAULT,
390
+ getDotClass(index),
391
+ ]"
392
+ :key="index"
393
+ v-for="(_, index) in totalPages"
394
+ />
395
+ </div>
332
396
  </div>
333
397
  </div>
334
398
 
@@ -248,7 +248,7 @@
248
248
 
249
249
  .tide-input-select:focus-within .tide-input-border {
250
250
  --tide-input-outline-width: var(--tide-border-width-2);
251
- outline-color: var(--tide-surface-border-high);
251
+ outline-color: var(--tide-border-high);
252
252
  }
253
253
 
254
254
  .tide-input-border {
@@ -222,7 +222,7 @@
222
222
 
223
223
  .tide-input-select:focus-within .tide-input-border {
224
224
  --tide-input-outline-width: var(--tide-border-width-2);
225
- outline-color: var(--tide-surface-border-high);
225
+ outline-color: var(--tide-border-high);
226
226
  }
227
227
 
228
228
  .tide-input-border {
@@ -320,7 +320,7 @@
320
320
 
321
321
  .tide-input-text:focus-within .tide-input-text-field {
322
322
  --tide-input-outline-width: var(--tide-border-width-2);
323
- outline-color: var(--tide-surface-border-high);
323
+ outline-color: var(--tide-border-high);
324
324
  }
325
325
 
326
326
  .tide-input-text input {
@@ -331,6 +331,6 @@
331
331
  --tide-input-outline-width: var(--tide-border-width-1);
332
332
  outline: var(--tide-input-outline-width) solid var(--tide-border);
333
333
  outline-offset: calc(var(--tide-input-outline-width) * -1);
334
- color: var(--tide-surface-foreground);
334
+ color: var(--tide-on-surface);
335
335
  }
336
336
  </style>
@@ -311,7 +311,7 @@
311
311
 
312
312
  .tide-input-text:focus-within .tide-input-text-field {
313
313
  --tide-input-outline-width: var(--tide-border-width-2);
314
- outline-color: var(--tide-surface-border-high);
314
+ outline-color: var(--tide-border-high);
315
315
  }
316
316
 
317
317
  .tide-input-text input {
@@ -322,6 +322,6 @@
322
322
  --tide-input-outline-width: var(--tide-border-width-1);
323
323
  outline: var(--tide-input-outline-width) solid var(--tide-border);
324
324
  outline-offset: calc(var(--tide-input-outline-width) * -1);
325
- color: var(--tide-surface-foreground);
325
+ color: var(--tide-on-surface);
326
326
  }
327
327
  </style>
@@ -189,7 +189,7 @@
189
189
 
190
190
  .tide-input-textarea:focus-within .tide-input-textarea-field {
191
191
  --tide-input-outline-width: var(--tide-border-width-2);
192
- outline-color: var(--tide-surface-border-high);
192
+ outline-color: var(--tide-border-high);
193
193
  }
194
194
 
195
195
  .tide-input-textarea textarea {
@@ -200,6 +200,6 @@
200
200
  --tide-input-outline-width: var(--tide-border-width-1);
201
201
  outline: var(--tide-input-outline-width) solid var(--tide-border);
202
202
  outline-offset: calc(var(--tide-input-outline-width) * -1);
203
- color: var(--tide-surface-foreground);
203
+ color: var(--tide-on-surface);
204
204
  }
205
205
  </style>
@@ -182,7 +182,7 @@
182
182
 
183
183
  .tide-input-textarea:focus-within .tide-input-textarea-field {
184
184
  --tide-input-outline-width: var(--tide-border-width-2);
185
- outline-color: var(--tide-surface-border-high);
185
+ outline-color: var(--tide-border-high);
186
186
  }
187
187
 
188
188
  .tide-input-textarea textarea {
@@ -193,6 +193,6 @@
193
193
  --tide-input-outline-width: var(--tide-border-width-1);
194
194
  outline: var(--tide-input-outline-width) solid var(--tide-border);
195
195
  outline-offset: calc(var(--tide-input-outline-width) * -1);
196
- color: var(--tide-surface-foreground);
196
+ color: var(--tide-on-surface);
197
197
  }
198
198
  </style>