tide-design-system 2.2.7 → 2.2.8

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.
package/package.json CHANGED
@@ -61,7 +61,7 @@
61
61
  "main": "dist/tide-design-system.cjs",
62
62
  "module": "dist/tide-design-system.esm.js",
63
63
  "types": "dist/tide-design-system.esm.d.ts",
64
- "version": "2.2.7",
64
+ "version": "2.2.8",
65
65
  "dependencies": {
66
66
  "@floating-ui/vue": "^1.1.6"
67
67
  }
@@ -6,6 +6,8 @@
6
6
  import { PRIORITY } from '@/types/Priority';
7
7
  import { CSS } from '@/types/Styles';
8
8
 
9
+ import type { Ref } from 'vue';
10
+
9
11
  type Props = {
10
12
  bleed?: number;
11
13
  isFloating?: boolean;
@@ -26,10 +28,16 @@
26
28
  title: undefined,
27
29
  });
28
30
 
29
- const emit = defineEmits(['change']);
31
+ type Emits = {
32
+ (event: 'slideChange', slideIndex: number): void;
33
+ (event: 'slidesAddedToView', slidesInView: number[]): void;
34
+ };
30
35
 
31
- const containerRef = ref();
36
+ const emit = defineEmits<Emits>();
32
37
 
38
+ const containerRef: Ref<HTMLDivElement | null> = ref(null);
39
+ const slideObserver = ref<IntersectionObserver | null>(null);
40
+ const slotObserver = ref<MutationObserver | null>(null);
33
41
  const contentRightEdge = ref();
34
42
  const contentWidth = ref();
35
43
  const currentSlide = ref(0);
@@ -42,6 +50,7 @@
42
50
 
43
51
  const updateContainerBleed = () => {
44
52
  if (props.bleed == undefined) return;
53
+ if (containerRef.value === null) return;
45
54
 
46
55
  if (isLastSlide.value && showButtons.value) {
47
56
  // Prevent gradient from bleeding off left edge in last position.
@@ -61,17 +70,21 @@
61
70
  * the frame.
62
71
  */
63
72
  const measureCurrentSlide = () => {
64
- // Get left offset of each slide.
65
- const slideOffsets = Array.from(containerRef.value.children).map((slide: any) => slide.offsetLeft);
73
+ const container = containerRef.value;
74
+ if (!container) return;
75
+
76
+ // Cast once, so TS knows these are HTMLElements
77
+ const slideOffsets = Array.from(container.children as HTMLCollectionOf<HTMLElement>).map(
78
+ (slide) => slide.offsetLeft
79
+ );
66
80
 
67
81
  if (slideOffsets.length === 0) return;
68
82
 
69
- // Get closest offset to current scroll position.
70
- const closestSlideOffset = slideOffsets.reduce((prev, curr) => {
71
- return Math.abs(curr - containerRef.value.scrollLeft) < Math.abs(prev - containerRef.value.scrollLeft)
72
- ? curr
73
- : prev;
74
- });
83
+ const scrollLeft = container.scrollLeft;
84
+
85
+ const closestSlideOffset = slideOffsets.reduce((prev, curr) =>
86
+ Math.abs(curr - scrollLeft) < Math.abs(prev - scrollLeft) ? curr : prev
87
+ );
75
88
 
76
89
  currentSlide.value = slideOffsets.indexOf(closestSlideOffset);
77
90
 
@@ -79,6 +92,7 @@
79
92
  };
80
93
 
81
94
  const measureDom = () => {
95
+ if (containerRef.value === null) return;
82
96
  contentRightEdge.value = containerRef.value.scrollLeft + containerRef.value.clientWidth;
83
97
  contentWidth.value = containerRef.value.scrollWidth;
84
98
  frameWidth.value = containerRef.value.clientWidth;
@@ -97,17 +111,19 @@
97
111
  };
98
112
 
99
113
  const scrollToSlide = (slideIndex: number) => {
114
+ if (containerRef.value === null) return;
100
115
  containerRef.value.scrollTo({
101
116
  behavior: 'smooth',
102
- left: containerRef.value.children[slideIndex].offsetLeft,
117
+ left: (containerRef.value.children[slideIndex] as HTMLElement).offsetLeft,
103
118
  });
104
119
  };
105
120
 
106
121
  const showNextSlide = () => {
122
+ if (containerRef.value === null) return;
107
123
  if (contentRightEdge.value > contentWidth.value) {
108
124
  containerRef.value.scrollTo({
109
125
  behavior: 'smooth',
110
- left: contentWidth,
126
+ left: contentWidth.value,
111
127
  });
112
128
  } else if (!isLastSlide.value) {
113
129
  scrollToSlide(currentSlide.value + 1);
@@ -120,13 +136,52 @@
120
136
  }
121
137
  };
122
138
 
139
+ const observeSlides = () => {
140
+ const options = {
141
+ root: containerRef?.value,
142
+ rootMargin: '0px',
143
+ threshold: 0.5,
144
+ };
145
+
146
+ slideObserver.value = new IntersectionObserver((entries) => {
147
+ let slides: number[] = [];
148
+
149
+ entries.forEach((entry) => {
150
+ const slideIndex = Array.from(containerRef.value?.children ?? []).indexOf(entry.target);
151
+ if (entry.isIntersecting) {
152
+ slides.push(slideIndex);
153
+ }
154
+ });
155
+
156
+ if (slides.length > 0) emit('slidesAddedToView', slides);
157
+ }, options);
158
+ Array.from(containerRef.value?.children ?? []).forEach((child) => {
159
+ if (slideObserver.value) slideObserver.value.observe(child);
160
+ });
161
+ };
162
+
163
+ const observeSlot = () => {
164
+ const observerCallback = () => {
165
+ observeSlides();
166
+ };
167
+ slotObserver.value = new MutationObserver(observerCallback);
168
+ if (containerRef.value) slotObserver.value.observe(containerRef.value, { childList: true });
169
+ };
170
+
123
171
  onMounted(() => {
172
+ if (window) {
173
+ window.addEventListener('resize', measureDom);
174
+ }
175
+
124
176
  measureDom();
125
- window.addEventListener('resize', measureDom);
177
+ observeSlides();
178
+ observeSlot();
126
179
  });
127
180
 
128
181
  onUnmounted(() => {
129
182
  window.removeEventListener('resize', measureDom);
183
+ slideObserver.value?.disconnect();
184
+ slotObserver.value?.disconnect();
130
185
  });
131
186
 
132
187
  onUpdated(() => {
@@ -134,7 +189,7 @@
134
189
  });
135
190
 
136
191
  watch(currentSlide, () => {
137
- emit('change', currentSlide.value);
192
+ emit('slideChange', currentSlide.value);
138
193
  });
139
194
  </script>
140
195
 
@@ -2,7 +2,7 @@ import { action } from '@storybook/addon-actions';
2
2
 
3
3
  import TideCard from '@/components/TideCard.vue';
4
4
  import TideCarousel from '@/components/TideCarousel.vue';
5
- import { argTypeBooleanUnrequired, change, disabledArgType, doSomething, lineBreak, tab } from '@/utilities/storybook';
5
+ import { argTypeBooleanUnrequired, disabledArgType, doSomething, lineBreak, tab } from '@/utilities/storybook';
6
6
 
7
7
  import type { StoryContext } from '@storybook/vue3';
8
8
 
@@ -17,7 +17,8 @@ const formatSnippet = (code: string, context: StoryContext) => {
17
17
  if (args.isTouchscreen !== undefined) argsWithValues.push(`:is-touchscreen="${args.isTouchscreen}"`);
18
18
  if (args.subtitle !== '') argsWithValues.push(`subtitle="${args.subtitle}"`);
19
19
  if (args.title !== '') argsWithValues.push(`title="${args.title}"`);
20
- if (args.handleChange) argsWithValues.push(`@change="${args.handleChange}"`);
20
+ if (args.handleSlideChange) argsWithValues.push(`@slide-change="${args.handleSlideChange}"`);
21
+ if (args.handleSlidesAddedToView) argsWithValues.push(`@slides-added-to-view="${args.handleSlidesAddedToView}"`);
21
22
 
22
23
  const slotContentMisc = args.misc === '' ? '' : `${tab}<template #misc>${args.misc}</template>${lineBreak}`;
23
24
 
@@ -46,17 +47,30 @@ const render = (args: any) => ({
46
47
  components: { TideCard, TideCarousel },
47
48
  methods: {
48
49
  doSomething,
49
- handleChange: (index: number) => {
50
- action(`Current slide ${index}`)(index);
50
+ handleSlideChange: (index: number) => {
51
+ action(`Current slide`)(index);
51
52
 
52
53
  try {
53
- const callback = eval(args.handleChange);
54
+ const callback = eval(args.handleSlideChange);
54
55
 
55
56
  if (callback) {
56
57
  callback();
57
58
  }
58
59
  } catch {
59
- alert('Please specify a valid handler in the "change" control.');
60
+ alert('Please specify a valid handler in the "slideChange" control.');
61
+ }
62
+ },
63
+ handleSlidesAddedToView: (slides: number[]) => {
64
+ action(`Slides added to view`)(slides);
65
+
66
+ try {
67
+ const callback = eval(args.handleSlidesAddedToView);
68
+
69
+ if (callback) {
70
+ callback();
71
+ }
72
+ } catch {
73
+ alert('Please specify a valid handler in the "slidesAddedToView" control.');
60
74
  }
61
75
  },
62
76
  },
@@ -64,7 +78,8 @@ const render = (args: any) => ({
64
78
  // prettier-ignore
65
79
  template:
66
80
  `<TideCarousel
67
- @change="handleChange"
81
+ @slide-change="handleSlideChange"
82
+ @slides-added-to-view="handleSlidesAddedToView"
68
83
  class="tide-margin-x-1"
69
84
  v-bind="args"
70
85
  >
@@ -74,7 +89,7 @@ const render = (args: any) => ({
74
89
  class="tide-shrink-none tide-border-1 tide-border tide-padding-1"
75
90
  v-for="(_child, index) in new Array(12)"
76
91
  >
77
- {{ args.default }}
92
+ {{ args.default.replace('#', index) }}
78
93
  </li>
79
94
  </TideCarousel>`,
80
95
  updated() {
@@ -96,7 +111,6 @@ export default {
96
111
  type: { summary: 'number' },
97
112
  },
98
113
  },
99
- change: disabledArgType,
100
114
  default: {
101
115
  control: 'text',
102
116
  description: 'Dynamic card content',
@@ -105,14 +119,28 @@ export default {
105
119
  type: { summary: 'HTML' },
106
120
  },
107
121
  },
108
- handleChange: {
109
- ...change,
122
+ handleSlideChange: {
123
+ control: 'text',
124
+ description: 'JS code or function to execute on slideChange event',
125
+ isEmit: true,
126
+ name: 'slideChange',
110
127
  table: {
111
128
  category: 'Events',
112
129
  defaultValue: { summary: 'None' },
113
130
  type: { summary: '(currentSlide: number) => void' },
114
131
  },
115
132
  },
133
+ handleSlidesAddedToView: {
134
+ control: 'text',
135
+ description: 'JS code or function to execute on slidesAddedToView event',
136
+ isEmit: true,
137
+ name: 'slidesAddedToView',
138
+ table: {
139
+ category: 'Events',
140
+ defaultValue: { summary: 'None' },
141
+ type: { summary: '(slides: number[]) => void' },
142
+ },
143
+ },
116
144
  isFloating: {
117
145
  ...argTypeBooleanUnrequired,
118
146
  description: 'Determines whether to display on-page or floating carousel',
@@ -150,6 +178,8 @@ export default {
150
178
  type: { summary: 'HTML' },
151
179
  },
152
180
  },
181
+ slideChange: disabledArgType,
182
+ slidesAddedToView: disabledArgType,
153
183
  subtitle: {
154
184
  control: 'text',
155
185
  description: 'Determines the text displayed to the right of the title',
@@ -169,8 +199,9 @@ export default {
169
199
  },
170
200
  args: {
171
201
  bleed: '',
172
- default: 'Card',
173
- handleChange: 'doSomething',
202
+ default: 'Card #',
203
+ handleSlideChange: 'doSomething',
204
+ handleSlidesAddedToView: 'doSomething',
174
205
  isFloating: undefined,
175
206
  isHideawayButtons: undefined,
176
207
  isTouchscreen: undefined,