@c4h/chuci 0.1.0 → 0.2.1

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.
@@ -1,362 +1,380 @@
1
- import { ChuciElement } from '@/utils/base-element'
2
- import Swiper from 'swiper'
3
- import { Navigation, Pagination, Scrollbar, Autoplay, Thumbs, Keyboard } from 'swiper/modules'
4
-
5
- // Import Swiper styles as strings to inject into shadow DOM
6
- import swiperStyles from './swiper-styles.css?inline'
7
-
8
- export class CcSwiper extends ChuciElement {
9
- private slider?: Swiper
10
- private divContainer?: HTMLDivElement
11
- private divSlides?: HTMLDivElement
12
- private divGallery?: HTMLDivElement
13
- private divPagination?: HTMLDivElement
14
- private divPrevious?: HTMLDivElement
15
- private divNext?: HTMLDivElement
16
-
17
- static get observedAttributes() {
18
- return ['has-thumb', 'autoplay']
19
- }
20
-
21
- get hasThumb() {
22
- return this.hasAttribute('has-thumb')
23
- }
24
-
25
- get autoplay() {
26
- return this.hasAttribute('autoplay')
27
- }
28
-
29
- get slides() {
30
- return [
31
- ...Array.from(this.querySelectorAll('cc-swiper-slide')),
32
- ...Array.from(this.divSlides?.querySelectorAll('cc-swiper-slide') ?? [])
33
- ]
34
- }
35
-
36
- async openViewer(imageUrl: string, imageType: string, slideIndex?: number) {
37
- let ccView = document.querySelector("cc-viewer")
38
- if (!ccView) {
39
- const viewerElement = document.createElement("cc-viewer")
40
- document.body.appendChild(viewerElement)
41
-
42
- // Wait for custom element to be defined and connected
43
- await customElements.whenDefined('cc-viewer')
44
-
45
- // Use a small timeout to ensure the element is fully initialized
46
- ccView = await new Promise((res) => {
47
- setTimeout(() => {
48
- res(document.querySelector("cc-viewer"))
49
- }, 100)
50
- })
51
- }
52
-
53
- // Store current swiper reference and slide index in viewer
54
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
55
- (ccView as any).setSwiper(this);
56
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
57
- (ccView as any).setCurrentSlideIndex(slideIndex ?? this.slider?.activeIndex ?? 0);
58
-
59
- // Get the slide element to extract attributes
60
- const slide = this.slides[slideIndex ?? this.slider?.activeIndex ?? 0];
61
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
62
- const attributes: Record<string, any> = {};
63
-
64
- // Check for viewer-specific attributes
65
- if (slide?.hasAttribute('fit-to-container')) {
66
- attributes.fitToContainer = true;
67
- }
68
- if (slide?.hasAttribute('debug-mode')) {
69
- attributes.debugMode = true;
70
- }
71
- if (slide?.hasAttribute('camera-position')) {
72
- attributes.cameraPosition = slide.getAttribute('camera-position');
73
- }
74
- if (slide?.hasAttribute('camera-target')) {
75
- attributes.cameraTarget = slide.getAttribute('camera-target');
76
- }
77
- if (slide?.hasAttribute('show-texture')) {
78
- attributes.showTexture = slide.getAttribute('show-texture') === 'true';
79
- }
80
-
81
- // For 3D models, pass material-url as attribute
82
- if (imageType === '3dmodel' && slide?.hasAttribute('material-url')) {
83
- attributes.materialUrl = slide.getAttribute('material-url');
84
- }
85
-
86
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
87
- ;(ccView as any).open(imageUrl, imageType, attributes)
88
- }
89
-
90
- protected firstUpdated() {
91
- // Initialization is now done in render method after DOM update
92
- }
93
-
94
- protected render() {
95
- // Inject Swiper styles
96
- const swiperStyleTag = `
97
- <style>
98
- ${swiperStyles}
99
- </style>
100
- `
101
-
102
- const styles = this.css`
103
- :host {
104
- display: block;
105
- height: 100%;
106
- width: 100%;
107
- --swiper-theme-color: var(--cc-slider-theme-color, #007aff);
108
- --swiper-navigation-color: var(--cc-slider-navigation-color, #007aff);
109
- --swiper-gallery-height: 0px;
110
- --swiper-slider-margin-bottom: 0px;
111
- --swiper-navigation-size: 44px;
112
- }
113
-
114
- :host([has-thumb]) {
115
- --swiper-slider-margin-bottom: 10px;
116
- --swiper-gallery-height: calc(100px - var(--swiper-slider-margin-bottom));
117
- }
118
-
119
- #divContainer {
120
- height: calc(100% - var(--swiper-gallery-height) - var(--swiper-slider-margin-bottom));
121
- margin-bottom: var(--swiper-slider-margin-bottom);
122
- }
123
-
124
- .swiper {
125
- height: 100%;
126
- }
127
-
128
- #divGallery {
129
- height: var(--swiper-gallery-height);
130
- }
131
-
132
- .gallery-thumbs .swiper-slide {
133
- height: 100%;
134
- opacity: 0.25;
135
- transition: 200ms;
136
- cursor: pointer;
137
- }
138
-
139
- .gallery-thumbs .swiper-slide-thumb-active {
140
- opacity: 1;
141
- }
142
-
143
- .gallery-thumb {
144
- background-position: center !important;
145
- background-repeat: no-repeat !important;
146
- background-size: cover !important;
147
- }
148
-
149
- .swiper-wrapper {
150
- text-align: center;
151
- }
152
-
153
- .swiper-slide {
154
- background-color: white;
155
- height: 100%;
156
- }
157
-
158
- img.viewer {
159
- object-fit: contain;
160
- height: 100%;
161
- width: 100%;
162
- cursor: pointer;
163
- pointer-events: auto !important;
164
- user-select: none;
165
- }
166
-
167
- img.viewer.w-caption {
168
- height: calc(100% - 10px - 1.5rem);
169
- }
170
-
171
- .slider-caption {
172
- padding: 5px;
173
- margin: 0;
174
- line-height: 1.5em;
175
- background: #000000;
176
- color: #ffffff;
177
- font-size: 0.6rem;
178
- font-weight: 700;
179
- position: absolute;
180
- bottom: 0;
181
- left: 0;
182
- right: 0;
183
- z-index: 10;
184
- }
185
-
186
- /* Adjust pagination position when caption exists */
187
- .swiper-pagination {
188
- bottom: 10px !important;
189
- }
190
-
191
- /* When captions exist, move pagination up */
192
- #divContainer.has-captions .swiper-pagination {
193
- bottom: calc(1.5rem + 20px) !important;
194
- }
195
-
196
- /* Navigation button styles with SVG icons */
197
- .swiper-button-prev,
198
- .swiper-button-next {
199
- color: var(--swiper-navigation-color);
200
- font-size: 0; /* Hide text */
201
- width: var(--swiper-navigation-size);
202
- height: var(--swiper-navigation-size);
203
- }
204
-
205
- .swiper-button-prev:after {
206
- content: '';
207
- display: block;
208
- width: var(--swiper-navigation-size);
209
- height: var(--swiper-navigation-size);
210
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23007aff'%3E%3Cpath d='M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z'/%3E%3C/svg%3E");
211
- background-size: contain;
212
- background-repeat: no-repeat;
213
- background-position: center;
214
- }
215
-
216
- .swiper-button-next:after {
217
- content: '';
218
- display: block;
219
- width: var(--swiper-navigation-size);
220
- height: var(--swiper-navigation-size);
221
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23007aff'%3E%3Cpath d='M8.59 16.59L10 18l6-6-6-6-1.41 1.41L13.17 12z'/%3E%3C/svg%3E");
222
- background-size: contain;
223
- background-repeat: no-repeat;
224
- background-position: center;
225
- }
226
- `
227
-
228
- const slidesHtml = this.slides.map((slide, index) => {
229
- const thumbnailUrl = slide.getAttribute('thumbnail-url') || ''
230
- const imageUrl = slide.getAttribute('image-url') || ''
231
- const imageType = slide.getAttribute('image-type') || 'image'
232
- const caption = slide.getAttribute('caption') || ''
233
-
234
- return `
235
- <div class='swiper-slide'>
236
- <img src="${thumbnailUrl}" data-image-url="${imageUrl}" data-image-type="${imageType}" data-index="${index}" class="viewer${caption !== "" ? ` w-caption` : ""}">
237
- ${caption !== "" ? `<p class="slider-caption">${caption}</p>` : ""}
238
- </div>
239
- `
240
- }).join('')
241
-
242
- const galleryHtml = this.slides.map((slide, index) => {
243
- const thumbnailUrl = slide.getAttribute('thumbnail-url') || ''
244
- return `
245
- <div class='swiper-slide gallery-thumb' data-index="${index}" style="background-image: url('${thumbnailUrl}')"></div>
246
- `
247
- }).join('')
248
-
249
- const html = `
250
- ${swiperStyleTag}
251
- ${styles}
252
- <div id='divContainer' class='swiper gallery-top'>
253
- <div id='divSlides' class='swiper-wrapper'>
254
- ${slidesHtml}
255
- </div>
256
-
257
- <div id='divPagination' class='swiper-pagination'></div>
258
- <div id='divPrevious' class='swiper-button-prev'></div>
259
- <div id='divNext' class='swiper-button-next'></div>
260
- </div>
261
- <div id='divGallery' class='swiper gallery-thumbs'>
262
- <div class='swiper-wrapper'>
263
- ${galleryHtml}
264
- </div>
265
- </div>
266
- `
267
-
268
- this.updateShadowRoot(html)
269
-
270
- // Initialize Swiper after DOM update
271
- setTimeout(() => {
272
- this.initializeSwiper()
273
-
274
- // Add click handlers for gallery thumbs
275
- this.queryAll('.gallery-thumb').forEach((thumb, index) => {
276
- thumb.addEventListener('click', () => this.slider?.slideTo(index))
277
- })
278
-
279
- // Add click handlers for viewer images
280
- this.queryAll('img.viewer').forEach((img) => {
281
- // Prevent default image behavior
282
- img.addEventListener('dragstart', (e) => e.preventDefault())
283
-
284
- img.addEventListener('click', (e) => {
285
- e.preventDefault()
286
- e.stopPropagation()
287
- e.stopImmediatePropagation()
288
- const target = e.target as HTMLImageElement
289
- const imageUrl = target.getAttribute('data-image-url') || ''
290
- const imageType = target.getAttribute('data-image-type') || 'image'
291
- const index = parseInt(target.getAttribute('data-index') || '0', 10)
292
- this.openViewer(imageUrl, imageType, index)
293
- return false
294
- }, true)
295
- })
296
- }, 0)
297
- }
298
-
299
- private initializeSwiper() {
300
- this.divContainer = this.query('#divContainer') ?? undefined
301
- this.divSlides = this.query('#divSlides') ?? undefined
302
- this.divGallery = this.query('#divGallery') ?? undefined
303
- this.divPagination = this.query('#divPagination') ?? undefined
304
- this.divPrevious = this.query('#divPrevious') ?? undefined
305
- this.divNext = this.query('#divNext') ?? undefined
306
-
307
- // Check if any slides have captions
308
- const hasCaptions = this.slides.some(slide => slide.getAttribute('caption'))
309
- if (hasCaptions && this.divContainer) {
310
- this.divContainer.classList.add('has-captions')
311
- }
312
-
313
- // Core library features at https://swiperjs.com/api/#custom-build
314
- const slidesLoop = this.slides.length >= 2
315
- if (!this.divContainer) return
316
-
317
- // Destroy existing slider if any
318
- if (this.slider) {
319
- this.slider.destroy()
320
- }
321
-
322
- this.slider = new Swiper(this.divContainer, {
323
- modules: [Navigation, Pagination, Scrollbar, Autoplay, Thumbs, Keyboard],
324
- navigation: {
325
- prevEl: this.divPrevious,
326
- nextEl: this.divNext,
327
- },
328
- pagination: this.hasThumb ? {} : {
329
- el: this.divPagination
330
- },
331
- autoplay: this.autoplay ? {
332
- delay: 5000,
333
- disableOnInteraction: false,
334
- reverseDirection: false,
335
- stopOnLastSlide: false,
336
- waitForTransition: true,
337
- } : false,
338
- thumbs: this.hasThumb && this.divGallery ? {
339
- swiper: new Swiper(this.divGallery, {
340
- spaceBetween: 10,
341
- slidesPerView: Math.min(Math.max(4, this.slides.length), 8),
342
- watchSlidesProgress: true,
343
- }),
344
- } : {},
345
- preventClicks: false,
346
- preventClicksPropagation: false,
347
- simulateTouch: true,
348
- allowTouchMove: true,
349
- loop: slidesLoop
350
- })
351
- }
352
- }
353
-
354
- if (!customElements.get('cc-swiper')) {
355
- customElements.define('cc-swiper', CcSwiper)
356
- }
357
-
358
- declare global {
359
- interface HTMLElementTagNameMap {
360
- 'cc-swiper': CcSwiper
361
- }
1
+ import { ChuciElement } from '@/utils/base-element'
2
+ import Swiper from 'swiper'
3
+ import { Navigation, Pagination, Scrollbar, Autoplay, Thumbs, Keyboard } from 'swiper/modules'
4
+
5
+ // Import Swiper styles as strings to inject into shadow DOM
6
+ import swiperStyles from './swiper-styles.css?inline'
7
+
8
+ export class CcSwiper extends ChuciElement {
9
+ private slider?: Swiper
10
+ private divContainer?: HTMLDivElement
11
+ private divSlides?: HTMLDivElement
12
+ private divGallery?: HTMLDivElement
13
+ private divPagination?: HTMLDivElement
14
+ private divPrevious?: HTMLDivElement
15
+ private divNext?: HTMLDivElement
16
+ private isDragging = false
17
+
18
+ static get observedAttributes() {
19
+ return ['has-thumb', 'autoplay']
20
+ }
21
+
22
+ get hasThumb() {
23
+ return this.hasAttribute('has-thumb')
24
+ }
25
+
26
+ get autoplay() {
27
+ return this.hasAttribute('autoplay')
28
+ }
29
+
30
+ get slides() {
31
+ return [
32
+ ...Array.from(this.querySelectorAll('cc-swiper-slide')),
33
+ ...Array.from(this.divSlides?.querySelectorAll('cc-swiper-slide') ?? [])
34
+ ]
35
+ }
36
+
37
+ async openViewer(imageUrl: string, imageType: string, slideIndex?: number) {
38
+ let ccView = document.querySelector("cc-viewer")
39
+ if (!ccView) {
40
+ const viewerElement = document.createElement("cc-viewer")
41
+ document.body.appendChild(viewerElement)
42
+
43
+ // Wait for custom element to be defined and connected
44
+ await customElements.whenDefined('cc-viewer')
45
+
46
+ // Use a small timeout to ensure the element is fully initialized
47
+ ccView = await new Promise((res) => {
48
+ setTimeout(() => {
49
+ res(document.querySelector("cc-viewer"))
50
+ }, 100)
51
+ })
52
+ }
53
+
54
+ // Store current swiper reference and slide index in viewer
55
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
56
+ (ccView as any).setSwiper(this);
57
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
58
+ (ccView as any).setCurrentSlideIndex(slideIndex ?? this.slider?.activeIndex ?? 0);
59
+
60
+ // Get the slide element to extract attributes
61
+ const slide = this.slides[slideIndex ?? this.slider?.activeIndex ?? 0];
62
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
63
+ const attributes: Record<string, any> = {};
64
+
65
+ // Check for viewer-specific attributes
66
+ if (slide?.hasAttribute('fit-to-container')) {
67
+ attributes.fitToContainer = true;
68
+ }
69
+ if (slide?.hasAttribute('debug-mode')) {
70
+ attributes.debugMode = true;
71
+ }
72
+ if (slide?.hasAttribute('camera-position')) {
73
+ attributes.cameraPosition = slide.getAttribute('camera-position');
74
+ }
75
+ if (slide?.hasAttribute('camera-target')) {
76
+ attributes.cameraTarget = slide.getAttribute('camera-target');
77
+ }
78
+ if (slide?.hasAttribute('show-texture')) {
79
+ attributes.showTexture = slide.getAttribute('show-texture') === 'true';
80
+ }
81
+
82
+ // For 3D models, pass material-url as attribute
83
+ if (imageType === '3dmodel' && slide?.hasAttribute('material-url')) {
84
+ attributes.materialUrl = slide.getAttribute('material-url');
85
+ }
86
+
87
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
88
+ ;(ccView as any).open(imageUrl, imageType, attributes)
89
+ }
90
+
91
+ protected firstUpdated() {
92
+ // Initialization is now done in render method after DOM update
93
+ }
94
+
95
+ protected render() {
96
+ // Inject Swiper styles
97
+ const swiperStyleTag = `
98
+ <style>
99
+ ${swiperStyles}
100
+ </style>
101
+ `
102
+
103
+ const styles = this.css`
104
+ :host {
105
+ display: block;
106
+ height: 100%;
107
+ width: 100%;
108
+ --swiper-theme-color: var(--cc-slider-theme-color, #007aff);
109
+ --swiper-navigation-color: var(--cc-slider-navigation-color, #007aff);
110
+ --swiper-gallery-height: 0px;
111
+ --swiper-slider-margin-bottom: 0px;
112
+ --swiper-navigation-size: 44px;
113
+ }
114
+
115
+ :host([has-thumb]) {
116
+ --swiper-slider-margin-bottom: 10px;
117
+ --swiper-gallery-height: calc(100px - var(--swiper-slider-margin-bottom));
118
+ }
119
+
120
+ #divContainer {
121
+ height: calc(100% - var(--swiper-gallery-height) - var(--swiper-slider-margin-bottom));
122
+ margin-bottom: var(--swiper-slider-margin-bottom);
123
+ }
124
+
125
+ .swiper {
126
+ height: 100%;
127
+ }
128
+
129
+ #divGallery {
130
+ height: var(--swiper-gallery-height);
131
+ }
132
+
133
+ .gallery-thumbs .swiper-slide {
134
+ height: 100%;
135
+ opacity: 0.25;
136
+ transition: 200ms;
137
+ cursor: pointer;
138
+ }
139
+
140
+ .gallery-thumbs .swiper-slide-thumb-active {
141
+ opacity: 1;
142
+ }
143
+
144
+ .gallery-thumb {
145
+ background-position: center !important;
146
+ background-repeat: no-repeat !important;
147
+ background-size: cover !important;
148
+ }
149
+
150
+ .swiper-wrapper {
151
+ text-align: center;
152
+ }
153
+
154
+ .swiper-slide {
155
+ background-color: white;
156
+ height: 100%;
157
+ }
158
+
159
+ img.viewer {
160
+ object-fit: contain;
161
+ height: 100%;
162
+ width: 100%;
163
+ cursor: pointer;
164
+ pointer-events: auto !important;
165
+ user-select: none;
166
+ }
167
+
168
+ img.viewer.w-caption {
169
+ height: calc(100% - 10px - 1.5rem);
170
+ }
171
+
172
+ .slider-caption {
173
+ padding: 5px;
174
+ margin: 0;
175
+ line-height: 1.5em;
176
+ background: #000000;
177
+ color: #ffffff;
178
+ font-size: 0.6rem;
179
+ font-weight: 700;
180
+ position: absolute;
181
+ bottom: 0;
182
+ left: 0;
183
+ right: 0;
184
+ z-index: 10;
185
+ }
186
+
187
+ /* Adjust pagination position when caption exists */
188
+ .swiper-pagination {
189
+ bottom: 10px !important;
190
+ }
191
+
192
+ /* When captions exist, move pagination up */
193
+ #divContainer.has-captions .swiper-pagination {
194
+ bottom: calc(1.5rem + 20px) !important;
195
+ }
196
+
197
+ /* Navigation button styles with SVG icons */
198
+ .swiper-button-prev,
199
+ .swiper-button-next {
200
+ color: var(--swiper-navigation-color);
201
+ font-size: 0; /* Hide text */
202
+ width: var(--swiper-navigation-size);
203
+ height: var(--swiper-navigation-size);
204
+ }
205
+
206
+ .swiper-button-prev:after {
207
+ content: '';
208
+ display: block;
209
+ width: var(--swiper-navigation-size);
210
+ height: var(--swiper-navigation-size);
211
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23007aff'%3E%3Cpath d='M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z'/%3E%3C/svg%3E");
212
+ background-size: contain;
213
+ background-repeat: no-repeat;
214
+ background-position: center;
215
+ }
216
+
217
+ .swiper-button-next:after {
218
+ content: '';
219
+ display: block;
220
+ width: var(--swiper-navigation-size);
221
+ height: var(--swiper-navigation-size);
222
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23007aff'%3E%3Cpath d='M8.59 16.59L10 18l6-6-6-6-1.41 1.41L13.17 12z'/%3E%3C/svg%3E");
223
+ background-size: contain;
224
+ background-repeat: no-repeat;
225
+ background-position: center;
226
+ }
227
+ `
228
+
229
+ const slidesHtml = this.slides.map((slide, index) => {
230
+ const thumbnailUrl = slide.getAttribute('thumbnail-url') || ''
231
+ const imageUrl = slide.getAttribute('image-url') || ''
232
+ const imageType = slide.getAttribute('image-type') || 'image'
233
+ const caption = slide.getAttribute('caption') || ''
234
+
235
+ return `
236
+ <div class='swiper-slide'>
237
+ <img src="${thumbnailUrl}" data-image-url="${imageUrl}" data-image-type="${imageType}" data-index="${index}" class="viewer${caption !== "" ? ` w-caption` : ""}">
238
+ ${caption !== "" ? `<p class="slider-caption">${caption}</p>` : ""}
239
+ </div>
240
+ `
241
+ }).join('')
242
+
243
+ const galleryHtml = this.slides.map((slide, index) => {
244
+ const thumbnailUrl = slide.getAttribute('thumbnail-url') || ''
245
+ return `
246
+ <div class='swiper-slide gallery-thumb' data-index="${index}" style="background-image: url('${thumbnailUrl}')"></div>
247
+ `
248
+ }).join('')
249
+
250
+ const html = `
251
+ ${swiperStyleTag}
252
+ ${styles}
253
+ <div id='divContainer' class='swiper gallery-top'>
254
+ <div id='divSlides' class='swiper-wrapper'>
255
+ ${slidesHtml}
256
+ </div>
257
+
258
+ <div id='divPagination' class='swiper-pagination'></div>
259
+ <div id='divPrevious' class='swiper-button-prev'></div>
260
+ <div id='divNext' class='swiper-button-next'></div>
261
+ </div>
262
+ <div id='divGallery' class='swiper gallery-thumbs'>
263
+ <div class='swiper-wrapper'>
264
+ ${galleryHtml}
265
+ </div>
266
+ </div>
267
+ `
268
+
269
+ this.updateShadowRoot(html)
270
+
271
+ // Initialize Swiper after DOM update
272
+ setTimeout(() => {
273
+ this.initializeSwiper()
274
+
275
+ // Add click handlers for gallery thumbs
276
+ this.queryAll('.gallery-thumb').forEach((thumb, index) => {
277
+ thumb.addEventListener('click', () => this.slider?.slideTo(index))
278
+ })
279
+
280
+ // Add click handlers for viewer images
281
+ this.queryAll('img.viewer').forEach((img) => {
282
+ // Prevent default image behavior
283
+ img.addEventListener('dragstart', (e) => e.preventDefault())
284
+
285
+ img.addEventListener('click', (e) => {
286
+ // Ignore click if user was dragging
287
+ if (this.isDragging) {
288
+ this.isDragging = false
289
+ return
290
+ }
291
+
292
+ e.preventDefault()
293
+ e.stopPropagation()
294
+ e.stopImmediatePropagation()
295
+ const target = e.target as HTMLImageElement
296
+ const imageUrl = target.getAttribute('data-image-url') || ''
297
+ const imageType = target.getAttribute('data-image-type') || 'image'
298
+ const index = parseInt(target.getAttribute('data-index') || '0', 10)
299
+ this.openViewer(imageUrl, imageType, index)
300
+ return false
301
+ }, true)
302
+ })
303
+ }, 0)
304
+ }
305
+
306
+ private initializeSwiper() {
307
+ this.divContainer = this.query('#divContainer') ?? undefined
308
+ this.divSlides = this.query('#divSlides') ?? undefined
309
+ this.divGallery = this.query('#divGallery') ?? undefined
310
+ this.divPagination = this.query('#divPagination') ?? undefined
311
+ this.divPrevious = this.query('#divPrevious') ?? undefined
312
+ this.divNext = this.query('#divNext') ?? undefined
313
+
314
+ // Check if any slides have captions
315
+ const hasCaptions = this.slides.some(slide => slide.getAttribute('caption'))
316
+ if (hasCaptions && this.divContainer) {
317
+ this.divContainer.classList.add('has-captions')
318
+ }
319
+
320
+ // Core library features at https://swiperjs.com/api/#custom-build
321
+ const slidesLoop = this.slides.length >= 2
322
+ if (!this.divContainer) return
323
+
324
+ // Destroy existing slider if any
325
+ if (this.slider) {
326
+ this.slider.destroy()
327
+ }
328
+
329
+ this.slider = new Swiper(this.divContainer, {
330
+ modules: [Navigation, Pagination, Scrollbar, Autoplay, Thumbs, Keyboard],
331
+ navigation: {
332
+ prevEl: this.divPrevious,
333
+ nextEl: this.divNext,
334
+ },
335
+ pagination: this.hasThumb ? {} : {
336
+ el: this.divPagination
337
+ },
338
+ autoplay: this.autoplay ? {
339
+ delay: 5000,
340
+ disableOnInteraction: false,
341
+ reverseDirection: false,
342
+ stopOnLastSlide: false,
343
+ waitForTransition: true,
344
+ } : false,
345
+ thumbs: this.hasThumb && this.divGallery ? {
346
+ swiper: new Swiper(this.divGallery, {
347
+ spaceBetween: 10,
348
+ slidesPerView: Math.min(Math.max(4, this.slides.length), 8),
349
+ watchSlidesProgress: true,
350
+ }),
351
+ } : {},
352
+ preventClicks: false,
353
+ preventClicksPropagation: false,
354
+ simulateTouch: true,
355
+ allowTouchMove: true,
356
+ loop: slidesLoop,
357
+ on: {
358
+ sliderMove: () => {
359
+ this.isDragging = true
360
+ },
361
+ touchEnd: () => {
362
+ // Reset dragging flag after a short delay to allow click event to check it
363
+ setTimeout(() => {
364
+ this.isDragging = false
365
+ }, 50)
366
+ }
367
+ }
368
+ })
369
+ }
370
+ }
371
+
372
+ if (!customElements.get('cc-swiper')) {
373
+ customElements.define('cc-swiper', CcSwiper)
374
+ }
375
+
376
+ declare global {
377
+ interface HTMLElementTagNameMap {
378
+ 'cc-swiper': CcSwiper
379
+ }
362
380
  }