zz-shopify-components 0.24.1-beta.2 → 0.25.0

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,447 +0,0 @@
1
- <script
2
- type='module'
3
- src='https://unpkg.com/@google/model-viewer/dist/model-viewer.min.js'
4
- ></script>
5
- <script
6
- {% comment %} defer {% endcomment %}
7
- nomodule
8
- src='https://unpkg.com/@google/model-viewer/dist/model-viewer-legacy.js'
9
- ></script>
10
-
11
- <style>
12
- .mv-container {
13
- position: relative;
14
- }
15
- .mv-view {
16
- position: absolute;
17
- inset: 0;
18
- opacity: 0;
19
- pointer-events: none;
20
- transition: opacity 280ms ease;
21
- }
22
- .mv-view.is-active {
23
- opacity: 1;
24
- pointer-events: auto;
25
- }
26
-
27
- .mv-toggle {
28
- position: absolute;
29
- bottom: 24px;
30
- left: 50%;
31
- transform: translateX(-50%);
32
- display: inline-flex;
33
- align-items: center;
34
- gap: 12px;
35
- padding: 0;
36
- background: transparent;
37
- box-shadow: none;
38
- z-index: 10;
39
- }
40
- .mv-toggle__btn {
41
- appearance: none;
42
- border: 0;
43
- cursor: pointer;
44
- background: transparent;
45
- padding: 0;
46
- margin: 0;
47
- font-weight: 600;
48
- font-size: 14px;
49
- line-height: 1;
50
- color: #9ca3af; /* gray-400 */
51
- }
52
- .mv-toggle__btn.is-active {
53
- color: #111827; /* gray-900 */
54
- font-weight: 600;
55
- }
56
- .mv-toggle__sep {
57
- color: #d1d5db; /* gray-300 */
58
- user-select: none;
59
- pointer-events: none;
60
- }
61
-
62
- .mv-fs-btn {
63
- position: absolute;
64
- bottom: 20px;
65
- right: 20px;
66
- width: 48px;
67
- height: 48px;
68
- display: inline-flex;
69
- align-items: center;
70
- justify-content: center;
71
- border-radius: 9999px;
72
- background: rgba(0, 0, 0, 0.3);
73
- color: #fff;
74
- border: 0;
75
- cursor: pointer;
76
- z-index: 11;
77
- }
78
- .mv-fs-btn:focus {
79
- outline: 2px solid rgba(255, 255, 255, 0.8);
80
- outline-offset: 2px;
81
- }
82
- </style>
83
-
84
- {% assign model_folded = product.metafields.custom.model_folded.value %}
85
- {% assign model_unfolded = product.metafields.custom.model_unfolded.value %}
86
- {% assign model_folded_poster = model_folded.preview_image
87
- | image_url: width: 656
88
- %}
89
- {% assign model_unfolded_poster = model_unfolded.preview_image
90
- | image_url: width: 656
91
- %}
92
-
93
- <div class=' tw-relative model-container max-lg:tw-hidden tw-w-full tw-h-full max-lg:tw-w-screen tw-rounded-2xl tw-overflow-hidden'>
94
- <div class=' tw-w-full tw-h-full tw-absolute tw-top-0 tw-left-0 mv-container'>
95
- <div class='mv-view mv-view--folded is-active' data-view='folded'>
96
- {% render 'zz-model-viewer',
97
- id: 'mv-folded-{{ block.id }}',
98
- src: model_folded.sources[0].url,
99
- poster: model_folded_poster,
100
- environment_image: 'https://se-cdn.djiits.com/uploads/3d/ce4f991f2121e53418c2978ce4d44709.hdr'
101
- %}
102
- </div>
103
-
104
- <div class='mv-view mv-view--unfolded' data-view='unfolded'>
105
- {% render 'zz-model-viewer',
106
- id: 'mv-unfolded-{{ block.id }}',
107
- src: model_unfolded.sources[0].url,
108
- poster: model_unfolded_poster,
109
- environment_image: 'https://se-cdn.djiits.com/uploads/3d/ce4f991f2121e53418c2978ce4d44709.hdr'
110
- %}
111
- </div>
112
- </div>
113
-
114
- <div class='mv-toggle' role='tablist' aria-label='Model toggle'>
115
- <button
116
- class='mv-toggle__btn is-active'
117
- data-toggle='unfolded'
118
- role='tab'
119
- aria-selected='true'
120
- >
121
- Unfolded
122
- </button>
123
- <span class='mv-toggle__sep' aria-hidden='true'>|</span>
124
- <button
125
- class='mv-toggle__btn'
126
- data-toggle='folded'
127
- role='tab'
128
- aria-selected='false'
129
- >
130
- Folded
131
- </button>
132
- </div>
133
- </div>
134
-
135
- <button
136
- id='mv-open-{{ block.id }}'
137
- type='button'
138
- class=' tw-w-full lg:tw-hidden tw-h-full tw-bg-[#F5F5F6] tw-flex tw-justify-center tw-items-center'
139
- >
140
- <img src='{{ model_folded_poster }}' alt='product model'>
141
-
142
- <svg
143
- width='36'
144
- height='36'
145
- viewBox='0 0 36 36'
146
- fill='none'
147
- class=' tw-absolute'
148
- xmlns='http://www.w3.org/2000/svg'
149
- >
150
- <rect x="0.5" y="0.5" width="35" height="35" rx="17.5" stroke="white"/>
151
- <path d="M24.4004 14.6953V21.4121L18 25.1064L11.5996 21.4121V14.6953L18 11L24.4004 14.6953Z" stroke="white" stroke-width="1.2"/>
152
- <path d="M24.2434 14.6886L18.0478 18.2656L11.8522 14.6886" stroke="white" stroke-width="1.2"/>
153
- <path d="M17.9999 24.9582V17.5488" stroke="white" stroke-width="1.2" stroke-linejoin="round"/>
154
- </svg>
155
- </button>
156
-
157
- <div
158
- id='mv-drawer-{{ block.id }}'
159
- class='mv-drawer lg:tw-hidden'
160
- aria-hidden='true'
161
- >
162
- <div class='mv-drawer__backdrop'></div>
163
- <div class='mv-drawer__panel'>
164
- <button
165
- class='mv-drawer__close'
166
- type='button'
167
- aria-label='Close'
168
- data-close
169
- >
170
- <svg
171
- width='24'
172
- height='24'
173
- viewBox='0 0 24 24'
174
- fill='none'
175
- xmlns='http://www.w3.org/2000/svg'
176
- >
177
- <path d='M6 6L18 18' stroke='#111' stroke-width='1.5' stroke-linecap='round'/>
178
- <path d='M18 6L6 18' stroke='#111' stroke-width='1.5' stroke-linecap='round'/>
179
- </svg>
180
- </button>
181
- <div class=' tw-w-full tw-h-full tw-absolute tw-top-0 tw-left-0 mv-container'>
182
- <div class='mv-view mv-mb-view--folded is-active' data-view='mb-folded'>
183
- {% render 'zz-model-viewer',
184
- id: 'mv-folded-mb-{{ block.id }}',
185
- src: model_folded.sources[0].url,
186
- poster: model_folded_poster,
187
- environment_image: 'https://se-cdn.djiits.com/uploads/3d/ce4f991f2121e53418c2978ce4d44709.hdr'
188
- %}
189
- </div>
190
-
191
- <div class='mv-view mv-mb-view--unfolded' data-view='mb-unfolded'>
192
- {% render 'zz-model-viewer',
193
- id: 'mv-unfolded-mb-{{ block.id }}',
194
- src: model_unfolded.sources[0].url,
195
- poster: model_unfolded_poster,
196
- environment_image: 'https://se-cdn.djiits.com/uploads/3d/ce4f991f2121e53418c2978ce4d44709.hdr'
197
- %}
198
- </div>
199
- </div>
200
-
201
- <div class='mv-toggle' role='tablist' aria-label='Model toggle'>
202
- <button
203
- class='mv-toggle__btn is-active'
204
- data-toggle='mb-unfolded'
205
- role='tab'
206
- aria-selected='true'
207
- >
208
- Unfolded
209
- </button>
210
- <span class='mv-toggle__sep' aria-hidden='true'>|</span>
211
- <button
212
- class='mv-toggle__btn'
213
- data-toggle='mb-folded'
214
- role='tab'
215
- aria-selected='false'
216
- >
217
- Folded
218
- </button>
219
- </div>
220
- </div>
221
- </div>
222
-
223
- <style>
224
- .mv-drawer {
225
- position: fixed;
226
- inset: 0;
227
- z-index: 60;
228
- visibility: hidden;
229
- pointer-events: none;
230
- }
231
- .mv-drawer.is-open {
232
- visibility: visible;
233
- pointer-events: auto;
234
- }
235
- .mv-drawer__backdrop {
236
- position: absolute;
237
- inset: 0;
238
- background: rgba(0, 0, 0, 0.5);
239
- opacity: 0;
240
- transition: opacity 200ms ease;
241
- }
242
- .mv-drawer.is-open .mv-drawer__backdrop {
243
- opacity: 1;
244
- }
245
- .mv-drawer__panel {
246
- position: absolute;
247
- left: 0;
248
- right: 0;
249
- bottom: 0;
250
- top: auto;
251
- height: 100%;
252
- background: #fff;
253
- transform: translateY(100%);
254
- transition: transform 300ms ease;
255
- display: flex;
256
- flex-direction: column;
257
- }
258
- .mv-drawer.is-open .mv-drawer__panel {
259
- transform: translateY(0);
260
- }
261
- .mv-drawer__close {
262
- position: absolute;
263
- top: 12px;
264
- right: 12px;
265
- width: 40px;
266
- height: 40px;
267
- display: inline-flex;
268
- align-items: center;
269
- justify-content: center;
270
- border-radius: 9999px;
271
- background: rgba(0, 0, 0, 0.06);
272
- border: 0;
273
- cursor: pointer;
274
- z-index: 1;
275
- }
276
- .mv-drawer__close:focus {
277
- outline: 2px solid rgba(17, 17, 17, 0.5);
278
- outline-offset: 2px;
279
- }
280
- </style>
281
-
282
- <script>
283
- (function () {
284
- const isMobile = window.matchMedia('(max-width: 1023.98px)').matches;
285
- if (!isMobile) return;
286
- const openBtn = document.getElementById('mv-open-{{ block.id }}');
287
- const drawer = document.getElementById('mv-drawer-{{ block.id }}');
288
- if (!openBtn || !drawer) return;
289
- const backdrop = drawer.querySelector('.mv-drawer__backdrop');
290
- const closeBtn = drawer.querySelector('[data-close]');
291
- const open = () => {
292
- drawer.classList.add('is-open');
293
- drawer.setAttribute('aria-hidden', 'false');
294
- document.documentElement.style.overflow = 'hidden';
295
- };
296
- const close = () => {
297
- drawer.classList.remove('is-open');
298
- drawer.setAttribute('aria-hidden', 'true');
299
- document.documentElement.style.overflow = '';
300
- };
301
- openBtn.addEventListener('click', open);
302
- if (backdrop) backdrop.addEventListener('click', close);
303
- if (closeBtn) closeBtn.addEventListener('click', close);
304
- document.addEventListener('keydown', (e) => {
305
- if (e.key === 'Escape') close();
306
- });
307
- })();
308
- </script>
309
-
310
- <script>
311
- (function () {
312
- // Scoped toggle logic for each toggle block (desktop and mobile)
313
- function initToggle(toggleRoot) {
314
- const scope = toggleRoot.closest('.model-container, .mv-drawer') || document;
315
- const container = scope.querySelector('.mv-container');
316
- if (!container) return;
317
- const btns = toggleRoot.querySelectorAll('.mv-toggle__btn');
318
- function setActive(key) {
319
- container.querySelectorAll('.mv-view').forEach((viewEl) => {
320
- const isActive = viewEl.getAttribute('data-view') === key;
321
- viewEl.classList.toggle('is-active', isActive);
322
- if (isActive) {
323
- viewEl.removeAttribute('inert');
324
- viewEl.setAttribute('aria-hidden', 'false');
325
- } else {
326
- viewEl.setAttribute('inert', '');
327
- viewEl.setAttribute('aria-hidden', 'true');
328
- }
329
- });
330
- btns.forEach((b) => {
331
- const active = b.getAttribute('data-toggle') === key;
332
- b.classList.toggle('is-active', active);
333
- b.setAttribute('aria-selected', active ? 'true' : 'false');
334
- });
335
- }
336
- btns.forEach((btn) => {
337
- btn.addEventListener('click', () => {
338
- setActive(btn.getAttribute('data-toggle'));
339
- });
340
- });
341
- const defaultKey =
342
- Array.from(btns).find(
343
- (b) => b.classList.contains('is-active') || b.getAttribute('aria-selected') === 'true'
344
- )?.getAttribute('data-toggle') || 'unfolded';
345
- setActive(defaultKey);
346
- }
347
- document.querySelectorAll('.mv-toggle').forEach(initToggle);
348
-
349
- const mv = document.querySelector('.mv-container');
350
- if (!mv) return;
351
- const parent = mv.parentElement; // wrapper with relative positioning
352
- if (!parent) return;
353
- if (window.innerWidth < 1024) {
354
- return;
355
- }
356
- const btnId = 'mv-fs-btn';
357
- let btn = document.getElementById(btnId);
358
- if (!btn) {
359
- btn = document.createElement('button');
360
- btn.type = 'button';
361
- btn.id = btnId;
362
- btn.className = 'mv-fs-btn';
363
- btn.setAttribute('aria-label', 'Enter fullscreen');
364
- btn.innerHTML =
365
- '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 4.5H19.5V9.5" stroke="white" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/><path d="M19.475 4.52515L13.8181 10.182" stroke="white" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/><path d="M9.5 19.5H4.5V14.5" stroke="white" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/><path d="M4.52502 19.4749L10.1819 13.818" stroke="white" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg>';
366
- parent.appendChild(btn);
367
- }
368
-
369
- const getActiveTarget = () =>
370
- document.querySelector('.mv-view.is-active model-viewer') ||
371
- document.querySelector('.mv-view.is-active') ||
372
- mv;
373
-
374
- const isFullscreenSupported = () => {
375
- const el = getActiveTarget();
376
- const elSupports = !!(
377
- el &&
378
- (el.requestFullscreen || el.webkitRequestFullscreen)
379
- );
380
- const docSupports = !!(
381
- document.fullscreenEnabled || document.webkitFullscreenEnabled
382
- );
383
- return true;
384
- };
385
-
386
- const isFs = () => {
387
- const el = getActiveTarget();
388
- return (
389
- document.fullscreenElement === el ||
390
- document.webkitFullscreenElement === el
391
- );
392
- };
393
-
394
- const enterFs = async () => {
395
- const el = getActiveTarget();
396
- try {
397
- if (!el) return;
398
- if (el.requestFullscreen) {
399
- await el.requestFullscreen({ navigationUI: 'hide' });
400
- } else if (el.webkitRequestFullscreen) {
401
- el.webkitRequestFullscreen();
402
- } else {
403
- throw new Error('Fullscreen not supported');
404
- }
405
- } catch (e) {}
406
- };
407
-
408
- const exitFs = async () => {
409
- try {
410
- if (document.fullscreenElement && document.exitFullscreen) {
411
- await document.exitFullscreen();
412
- } else if (
413
- document.webkitFullscreenElement &&
414
- document.webkitExitFullscreen
415
- ) {
416
- document.webkitExitFullscreen();
417
- }
418
- } catch (_) {}
419
- };
420
-
421
- const toggleFs = () => {
422
- if (isFs()) {
423
- exitFs();
424
- } else {
425
- enterFs();
426
- }
427
- };
428
-
429
- const onFsChange = () => {
430
- const active = isFs();
431
- btn.setAttribute('aria-pressed', active ? 'true' : 'false');
432
- btn.setAttribute(
433
- 'aria-label',
434
- active ? 'Exit fullscreen' : 'Enter fullscreen'
435
- );
436
- };
437
-
438
- btn.addEventListener('click', toggleFs);
439
- document.addEventListener('fullscreenchange', onFsChange);
440
- document.addEventListener('webkitfullscreenchange', onFsChange);
441
-
442
- // Hide fullscreen button if unsupported
443
- if (!isFullscreenSupported()) {
444
- btn.style.display = 'none';
445
- }
446
- })();
447
- </script>
@@ -1,51 +0,0 @@
1
- <div class='tw-w-full tw-max-w-[1680px] tw-mx-auto'>
2
- <div class='product-container tw-flex tw-items-center tw-justify-center'>
3
- {% comment %} 左侧产品预览 {% endcomment %}
4
- <div
5
- class='product-preview tw-w-full tw-aspect-square md:tw-aspect-[16/9]'
6
- data-test-locator='productPreview'
7
- >
8
- <div
9
- class='product-swiper product-preview-item active'
10
- data-test-locator='productPreviewSwiper'
11
- data-gallery-value='photo'
12
- >
13
- {% render 'zz-product-swiper' %}
14
- </div>
15
- {% comment %} 产品模型 {% endcomment %}
16
- {% if show_model %}
17
- <div
18
- class='product-model product-preview-item'
19
- data-test-locator='productPreviewModel'
20
- data-gallery-value='3d'
21
- >
22
- {% render 'zz-product-model' %}
23
- </div>
24
- {% endif %}
25
- {% comment %} 配件标注点 {% endcomment %}
26
- {% if show_accessories_point %}
27
- {%- if accessories_points.size > 0 -%}
28
- <div
29
- class='product-accessories-point product-preview-item'
30
- data-test-locator='productPreviewAccessoriesPoint'
31
- data-gallery-value='accessories'
32
- >
33
- {% render 'zz-accessories-preview-swiper',
34
- accessories_points: accessories_points
35
- %}
36
- </div>
37
- {%- endif -%}
38
- {%- endif -%}
39
- {% if show_video %}
40
- <div
41
- class='product-video product-preview-item'
42
- data-test-locator='productPreviewVideo'
43
- data-gallery-value='video'
44
- >
45
- {%- render 'zz-product-video' -%}
46
- </div>
47
- {% endif %}
48
- </div>
49
- </div>
50
- </div>
51
- {% render 'zz-gallery-tab' show_accessories_point: show_accessories_point, show_video: show_video, show_model: show_model %}