@designcrowd/library.brand-page 5.17.21 → 5.17.22
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/lib/src/{CreatedOnBrandCrowd-de-de-Brtp2yWB.js → CreatedOnBrandCrowd-de-de-CE3q_ouk.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-BIJ5VoX4.js → CreatedOnBrandCrowd-en-us-YmCINEd2.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-JrKK9K0u.js → CreatedOnBrandCrowd-es-es-po716FZh.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-D_HvOZe-.js → CreatedOnBrandCrowd-fr-ca-DgSeh7zq.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-D_HvOZe-.js → CreatedOnBrandCrowd-fr-fr-DgSeh7zq.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-SSLb0vlp.js → CreatedOnBrandCrowd-pt-br-BY3I3OVc.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-SSLb0vlp.js → CreatedOnBrandCrowd-pt-pt-BY3I3OVc.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-BG39ozFo.js → CreatedOnDcom-de-de-VF__9AH9.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-D2NzqECZ.js → CreatedOnDcom-en-us-B653tDEJ.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-C55r2Lb4.js → CreatedOnDcom-es-es-Cc3g0BMj.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-Bj-sAaJI.js → CreatedOnDcom-fr-ca-NzxsUZqx.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-D8l0r-Tc.js → CreatedOnDcom-fr-fr-DFuj0umw.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-CXHC6E1h.js → CreatedOnDcom-pt-br-VxpFX1Jd.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-2dMvgnyi.js → CreatedOnDcom-pt-pt-CG2qMIlw.js} +1 -1
- package/lib/src/brand-page/components/image-gallery/ImageGalleryModal.mixin.d.ts +8 -1
- package/lib/src/brand-page/components/image-gallery/ImageGalleryModal.mixin.js +97 -3
- package/lib/src/css/library.brand-page-brandCrowd.min.css +11 -6
- package/lib/src/css/library.brand-page-brandPage.min.css +11 -6
- package/lib/src/css/library.brand-page-designCom.min.css +11 -6
- package/lib/src/css/library.brand-page-preview.min.css +11 -6
- package/lib/src/index.mjs +1 -1
- package/lib/src/{lib-CrBvj1a5.js → lib-DO1Zk3d3.js} +3561 -3515
- package/package.json +1 -1
- package/src/brand-page/components/image-gallery/ImageGalleryModal.mixin.ts +114 -5
- package/src/brand-page/components/image-gallery/ImageGalleryModal.vue +25 -8
package/package.json
CHANGED
|
@@ -5,6 +5,15 @@ import ImageModalMixin from '../mixins/image-modal.mixin';
|
|
|
5
5
|
import cloneDeep from 'lodash.clonedeep';
|
|
6
6
|
import type { BrandPageContext, ImageItem } from '@brand-page/models';
|
|
7
7
|
|
|
8
|
+
// Number of slides on each side of the current slide to keep mounted with a
|
|
9
|
+
// real <img>. Slides outside this window render an empty placeholder so the
|
|
10
|
+
// browser does not request full-resolution images for the entire gallery on
|
|
11
|
+
// modal open — required to prevent mobile tab crashes on large galleries.
|
|
12
|
+
// 3 (= 7 slides total) is wide enough that a user dragging slowly across
|
|
13
|
+
// 2-3 slides never exposes a placeholder, while still capping decoded
|
|
14
|
+
// image memory well under mobile per-tab limits.
|
|
15
|
+
const IMAGE_RENDER_WINDOW = 3;
|
|
16
|
+
|
|
8
17
|
export default defineComponent({
|
|
9
18
|
mixins: [ComponentCommonMixin, ImageModalMixin],
|
|
10
19
|
props: {
|
|
@@ -40,6 +49,7 @@ export default defineComponent({
|
|
|
40
49
|
isImageModalLoading: false,
|
|
41
50
|
carouselImages: [] as any[][] | undefined,
|
|
42
51
|
showControl: true,
|
|
52
|
+
carouselComponentRef: 'image-gallery-carousel',
|
|
43
53
|
};
|
|
44
54
|
},
|
|
45
55
|
computed: {
|
|
@@ -65,7 +75,7 @@ export default defineComponent({
|
|
|
65
75
|
|
|
66
76
|
swiperOptions() {
|
|
67
77
|
return {
|
|
68
|
-
initialSlide: 0,
|
|
78
|
+
initialSlide: this.selectedImageIndex ?? 0,
|
|
69
79
|
loop: this.images.length > 1,
|
|
70
80
|
slidesPerGroup: 1,
|
|
71
81
|
slidesPerView: 1,
|
|
@@ -99,10 +109,6 @@ export default defineComponent({
|
|
|
99
109
|
}
|
|
100
110
|
|
|
101
111
|
this.isImageModalLoading = true;
|
|
102
|
-
|
|
103
|
-
this.swiperOptions.initialSlide = newIndex;
|
|
104
|
-
|
|
105
|
-
// this.$nextTick(() => this.checkImageModalImageComplete());
|
|
106
112
|
},
|
|
107
113
|
|
|
108
114
|
images: {
|
|
@@ -154,5 +160,108 @@ export default defineComponent({
|
|
|
154
160
|
onImageModalClose() {
|
|
155
161
|
this.$emit('close');
|
|
156
162
|
},
|
|
163
|
+
|
|
164
|
+
isSlideInRenderWindow(index: number): boolean {
|
|
165
|
+
if (this.currentImageIndex === undefined) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const total = this.images.length;
|
|
170
|
+
|
|
171
|
+
if (total === 0) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const direct = Math.abs(index - this.currentImageIndex);
|
|
176
|
+
// For looped galleries, the shortest path between two slides may wrap
|
|
177
|
+
// around the end. Use whichever distance is smaller.
|
|
178
|
+
const wrapped = total - direct;
|
|
179
|
+
const distance = Math.min(direct, wrapped);
|
|
180
|
+
|
|
181
|
+
return distance <= IMAGE_RENDER_WINDOW;
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
onCarouselLoaded() {
|
|
185
|
+
// Reach through the Carousel ref to bind directly to the underlying
|
|
186
|
+
// Swiper instance — the shared Carousel doesn't emit slide-change
|
|
187
|
+
// events. Keeps `currentImageIndex` in sync with user navigation
|
|
188
|
+
// (arrows, swipes, keyboard) so the render window slides with the
|
|
189
|
+
// user.
|
|
190
|
+
//
|
|
191
|
+
// NOTE: `currentImageIndex` must not be bound back to Carousel's
|
|
192
|
+
// `:slide-index` prop. The shared Carousel watches that prop and
|
|
193
|
+
// calls `swiper.update()` + `swiper.slideTo()` on every change.
|
|
194
|
+
// With `slideChangeTransitionStart` updating currentImageIndex on
|
|
195
|
+
// every swipe, that creates a feedback loop where Swiper redoes a
|
|
196
|
+
// full layout recalculation on every user gesture — visible as
|
|
197
|
+
// jank and capable of crashing the tab on real mobile devices.
|
|
198
|
+
const carousel = this.$refs[this.carouselComponentRef] as any;
|
|
199
|
+
const swiper = carousel?.swiper;
|
|
200
|
+
|
|
201
|
+
if (!swiper || typeof swiper.on !== 'function') {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Listen at the *start* of the transition so the incoming neighbour
|
|
206
|
+
// mounts and starts loading while the slide animation is still
|
|
207
|
+
// playing. Listening on `slideChange` (end of transition) leaves a
|
|
208
|
+
// gap during fast swipes where the next neighbour is still a
|
|
209
|
+
// placeholder when it reaches the viewport — visible as a flicker.
|
|
210
|
+
const handleSlideChange = () => {
|
|
211
|
+
// `realIndex` reflects the underlying slide index when loop mode
|
|
212
|
+
// duplicates slides; `activeIndex` would include those duplicates.
|
|
213
|
+
const next =
|
|
214
|
+
typeof swiper.realIndex === 'number'
|
|
215
|
+
? swiper.realIndex
|
|
216
|
+
: swiper.activeIndex;
|
|
217
|
+
|
|
218
|
+
if (typeof next === 'number' && next !== this.currentImageIndex) {
|
|
219
|
+
this.currentImageIndex = next;
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
swiper.on('slideChangeTransitionStart', handleSlideChange);
|
|
224
|
+
// Also handle instant jumps that bypass the transition (e.g. when
|
|
225
|
+
// navigation arrows trigger no-animation slides).
|
|
226
|
+
swiper.on('slideChange', handleSlideChange);
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
// Only the currently-viewed slide's load lifecycle should drive the
|
|
230
|
+
// modal's Loader. Neighbour FallbackImages mounted by the render window
|
|
231
|
+
// emit `image-loading` synchronously in their onMounted hook; without
|
|
232
|
+
// these gates that emission flips `isImageModalLoading` true mid-swipe,
|
|
233
|
+
// briefly flashing the spinner over the slide the user is actually on
|
|
234
|
+
// — the flicker reported on real mobile devices.
|
|
235
|
+
onSlideImageLoading(index: number) {
|
|
236
|
+
if (index === this.currentImageIndex) {
|
|
237
|
+
this.imageLoading();
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
onSlideImageLoaded(index: number) {
|
|
242
|
+
if (index === this.currentImageIndex) {
|
|
243
|
+
this.checkImageLoading(false);
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
onSlideImageError(index: number) {
|
|
248
|
+
if (index === this.currentImageIndex) {
|
|
249
|
+
this.checkImageLoading(true);
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
placeholderStyle(slide: any): Record<string, string> {
|
|
254
|
+
// Sized so the placeholder occupies the exact space the loaded image
|
|
255
|
+
// would, preventing slide-height jumps when the render window swaps
|
|
256
|
+
// between placeholder and real image. Mirrors the image's own
|
|
257
|
+
// `max-width: 100%` + `max-height: 90vh` + intrinsic aspect ratio.
|
|
258
|
+
if (!slide?.width || !slide?.height) {
|
|
259
|
+
return {};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
aspectRatio: `${slide.width} / ${slide.height}`,
|
|
264
|
+
};
|
|
265
|
+
},
|
|
157
266
|
},
|
|
158
267
|
});
|
|
@@ -6,13 +6,15 @@
|
|
|
6
6
|
@modal-closed="onImageModalClose"
|
|
7
7
|
>
|
|
8
8
|
<Carousel
|
|
9
|
+
v-if="isImageModalVisible"
|
|
10
|
+
:ref="carouselComponentRef"
|
|
9
11
|
:classes="['tw-h-full', 'tw-max-h-full', 'tw-justify-center']"
|
|
10
12
|
:cards="carouselImages"
|
|
11
13
|
:options="swiperOptions"
|
|
12
|
-
:slide-index="currentImageIndex"
|
|
13
14
|
:show-bullets="false"
|
|
14
15
|
:show-controls="showCarouselControls"
|
|
15
16
|
:navigation-unique-indexer="navigationUniqueIndexer"
|
|
17
|
+
@on-carousel-load="onCarouselLoaded"
|
|
16
18
|
>
|
|
17
19
|
<template #arrow-left>
|
|
18
20
|
<div id="button-prev" :class="navigationPrevClass">
|
|
@@ -30,7 +32,7 @@
|
|
|
30
32
|
/>
|
|
31
33
|
</div>
|
|
32
34
|
</template>
|
|
33
|
-
<template #slide="{ slide }">
|
|
35
|
+
<template #slide="{ slide, index }">
|
|
34
36
|
<div
|
|
35
37
|
class="tw-text-center tw-flex tw-flex-col tw-items-center tw-justify-center tw-overflow-auto tw-h-auto"
|
|
36
38
|
:class="{ 'tw-justify-center': !slide.imageTitle }"
|
|
@@ -41,18 +43,27 @@
|
|
|
41
43
|
>
|
|
42
44
|
{{ slide.imageTitle }}
|
|
43
45
|
</h3>
|
|
44
|
-
<Loader
|
|
46
|
+
<Loader
|
|
47
|
+
v-if="isImageModalLoading && index === currentImageIndex"
|
|
48
|
+
class="tw-m-28"
|
|
49
|
+
/>
|
|
45
50
|
<FallbackImage
|
|
46
|
-
|
|
47
|
-
:
|
|
51
|
+
v-if="isSlideInRenderWindow(index)"
|
|
52
|
+
:key="`modal-slide-${index}`"
|
|
53
|
+
:ref="index === currentImageIndex ? imageModalImageRef : undefined"
|
|
48
54
|
:image-url="slide.imageUrl"
|
|
49
55
|
class="c-modal-image"
|
|
50
56
|
:alt="slide.imageTitle || altText"
|
|
51
57
|
:context="context"
|
|
52
58
|
:content-index="contentIndex"
|
|
53
|
-
@image-loaded="
|
|
54
|
-
@image-loading="
|
|
55
|
-
@image-error="
|
|
59
|
+
@image-loaded="onSlideImageLoaded(index)"
|
|
60
|
+
@image-loading="onSlideImageLoading(index)"
|
|
61
|
+
@image-error="onSlideImageError(index)"
|
|
62
|
+
/>
|
|
63
|
+
<div
|
|
64
|
+
v-else
|
|
65
|
+
class="c-modal-image-placeholder"
|
|
66
|
+
:style="placeholderStyle(slide)"
|
|
56
67
|
/>
|
|
57
68
|
</div>
|
|
58
69
|
</template>
|
|
@@ -115,4 +126,10 @@ export default {
|
|
|
115
126
|
@apply tw-max-w-full;
|
|
116
127
|
max-height: 90vh;
|
|
117
128
|
}
|
|
129
|
+
|
|
130
|
+
.c-modal-image-placeholder {
|
|
131
|
+
@apply tw-max-w-full;
|
|
132
|
+
width: 100%;
|
|
133
|
+
max-height: 90vh;
|
|
134
|
+
}
|
|
118
135
|
</style>
|