zz-shopify-components 0.3.1-beta.1 → 0.3.1-beta.10
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/assets/zz-components.css +3 -1
- package/assets/zz-components.js +3 -3
- package/assets/zz-http-request.js +27 -0
- package/assets/zz-modal.js +418 -0
- package/blocks/zz-button.liquid +13 -1
- package/blocks/zz-normal-swiper.liquid +246 -0
- package/docs/zz-modal.md +186 -0
- package/package.json +1 -1
- package/sections/zz-flex-layout-section.liquid +6 -1
- package/sections/zz-modal.liquid +157 -0
- package/sections/zz-navigation-tab-v3.liquid +5 -5
- package/sections/zz-shopping-card-list.liquid +0 -3
- package/sections/zz-swiper-banner.liquid +856 -0
- package/sections/zz-video-collapse-swiper.liquid +3 -30
- package/sections/zz-video-tab-swiper.liquid +0 -9
- package/snippets/zz-button.liquid +4 -1
package/assets/zz-components.css
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
align-items: center;
|
|
5
5
|
height: 36px;
|
|
6
6
|
border-radius: 32px;
|
|
7
|
-
background: #
|
|
7
|
+
background: #F5F5F6;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
.zz-radio-tabs-black.zz-radio-tabs {
|
|
@@ -125,10 +125,12 @@ zz-radio-tabs-item {
|
|
|
125
125
|
}
|
|
126
126
|
.content-text ol {
|
|
127
127
|
list-style: decimal;
|
|
128
|
+
padding-left: 20px;
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
.content-text ul {
|
|
131
132
|
list-style: disc;
|
|
133
|
+
padding-left: 20px;
|
|
132
134
|
}
|
|
133
135
|
|
|
134
136
|
/* 标题 */
|
package/assets/zz-components.js
CHANGED
|
@@ -7,13 +7,13 @@ class ZZRadioTabsItem extends HTMLElement {
|
|
|
7
7
|
// 创建radio input
|
|
8
8
|
const groupName =
|
|
9
9
|
this.closest('zz-radio-tabs')?.getAttribute('name') || 'option';
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
// 获取checked属性 - 支持checked和checked="true"两种方式
|
|
11
|
+
const checked = this.hasAttribute('checked');
|
|
12
12
|
const value = this.getAttribute('value') || '';
|
|
13
13
|
const slot = this.innerHTML;
|
|
14
14
|
this.innerHTML = `
|
|
15
15
|
<label class="zz-radio-tabs-wrapper">
|
|
16
|
-
<input type="radio" name=${groupName} value=${value}>
|
|
16
|
+
<input type="radio" name=${groupName} value=${value} ${checked ? 'checked' : ''}>
|
|
17
17
|
<div class="zz-radio-tabs-label">
|
|
18
18
|
${slot}
|
|
19
19
|
</div>
|
|
@@ -50,6 +50,30 @@
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
function requestFormData(method, url, formData, options = {}) {
|
|
54
|
+
const baseUrl = options.baseUrl || DEFAULT_BASE_URL;
|
|
55
|
+
const timeout = options.timeout || DEFAULT_TIMEOUT;
|
|
56
|
+
const fullUrl = baseUrl + url;
|
|
57
|
+
|
|
58
|
+
let fetchOptions = {
|
|
59
|
+
method: method.toUpperCase(),
|
|
60
|
+
headers: {
|
|
61
|
+
// 不设置Content-Type,让浏览器自动设置multipart/form-data
|
|
62
|
+
...(options.headers || {}),
|
|
63
|
+
},
|
|
64
|
+
...options,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// 移除可能存在的Content-Type,让浏览器自动处理FormData
|
|
68
|
+
delete fetchOptions.headers['Content-Type'];
|
|
69
|
+
|
|
70
|
+
fetchOptions.body = formData;
|
|
71
|
+
|
|
72
|
+
return timeoutFetch(fetch(fullUrl, fetchOptions), timeout).then(
|
|
73
|
+
handleResponse
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
53
77
|
function handleResponse(response) {
|
|
54
78
|
if (!response.ok) {
|
|
55
79
|
if (response.status === 401) {
|
|
@@ -69,5 +93,8 @@
|
|
|
69
93
|
post: function (url, params = {}, options = {}) {
|
|
70
94
|
return request('post', url, params, options);
|
|
71
95
|
},
|
|
96
|
+
formData: function (url, formData, options = {}) {
|
|
97
|
+
return requestFormData('post', url, formData, options);
|
|
98
|
+
},
|
|
72
99
|
};
|
|
73
100
|
})();
|
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
/*
|
|
2
|
+
- Uses <dialog> when available for native a11y and focus management
|
|
3
|
+
- Shadow DOM for internal styles; slots for user content (tailwind utilities apply to slotted nodes)
|
|
4
|
+
- Attribute reflection and programmatic API: show(), hide(), toggle()
|
|
5
|
+
- Options via attributes: close-on-esc, close-on-backdrop, scroll-lock, inert-others
|
|
6
|
+
- Global open/close triggers: [data-zz-modal-target], [data-zz-modal-close]
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
(() => {
|
|
10
|
+
if (customElements.get('zz-modal')) return;
|
|
11
|
+
|
|
12
|
+
const STYLE_TEXT = `
|
|
13
|
+
:host {
|
|
14
|
+
position: relative;
|
|
15
|
+
display: contents; /* do not affect layout where placed */
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
dialog[part="dialog"] {
|
|
19
|
+
border: none;
|
|
20
|
+
padding: 0;
|
|
21
|
+
margin: 0;
|
|
22
|
+
width: auto;
|
|
23
|
+
max-width: var(--zz-modal-max-width, 90vw);
|
|
24
|
+
max-height: var(--zz-modal-max-height, 85vh);
|
|
25
|
+
background: transparent; /* panel carries background */
|
|
26
|
+
overflow: visible;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* Backdrop for native <dialog>. */
|
|
30
|
+
dialog[part="dialog"]::backdrop {
|
|
31
|
+
background: var(--zz-modal-backdrop, rgba(0,0,0,0.5));
|
|
32
|
+
backdrop-filter: var(--zz-modal-backdrop-filter, blur(0px));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.panel {
|
|
36
|
+
will-change: transform, opacity;
|
|
37
|
+
position: fixed;
|
|
38
|
+
inset: 0;
|
|
39
|
+
z-index: var(--zz-modal-z-index, 9999);
|
|
40
|
+
display: grid;
|
|
41
|
+
place-items: center;
|
|
42
|
+
pointer-events: none; /* enable backdrop clicks via dialog */
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.panel-inner {
|
|
46
|
+
will-change: transform, opacity;
|
|
47
|
+
pointer-events: auto;
|
|
48
|
+
background: var(--zz-modal-background, #ffffff);
|
|
49
|
+
color: inherit;
|
|
50
|
+
border-radius: var(--zz-modal-radius, 12px);
|
|
51
|
+
box-shadow: var(--zz-modal-shadow, 0 20px 60px rgba(0,0,0,0.2));
|
|
52
|
+
width: var(--zz-modal-width, min(720px, 92vw));
|
|
53
|
+
max-height: var(--zz-modal-max-height, 90vh);
|
|
54
|
+
display: flex;
|
|
55
|
+
flex-direction: column;
|
|
56
|
+
overflow: hidden;
|
|
57
|
+
opacity: 0;
|
|
58
|
+
transition: none;
|
|
59
|
+
}
|
|
60
|
+
@media (min-width: 768px) {
|
|
61
|
+
.panel-inner {
|
|
62
|
+
transform-origin: center center;
|
|
63
|
+
transform: translateY(8px) scale(0.98);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
:host([open]) .panel-inner {
|
|
68
|
+
opacity: 1;
|
|
69
|
+
transform: translateY(0) scale(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* 移动端底部抽屉模式 */
|
|
73
|
+
@media (max-width: 768px) {
|
|
74
|
+
:host([sheet-on-mobile]) .panel { place-items: end center; }
|
|
75
|
+
:host([sheet-on-mobile]) .panel-inner {
|
|
76
|
+
width: var(--zz-sheet-width, 100%);
|
|
77
|
+
max-width: 100%;
|
|
78
|
+
border-top-left-radius: var(--zz-modal-radius, 12px);
|
|
79
|
+
border-top-right-radius: var(--zz-modal-radius, 12px);
|
|
80
|
+
border-bottom-left-radius: 0;
|
|
81
|
+
border-bottom-right-radius: 0;
|
|
82
|
+
margin: 0;
|
|
83
|
+
transform: translateY(100%) scale(1);
|
|
84
|
+
}
|
|
85
|
+
:host([sheet-on-mobile][open]) .panel-inner { transform: translateY(0) scale(1); }
|
|
86
|
+
:host([sheet-on-mobile]) .body { padding-bottom: calc(var(--zz-modal-padding, 16px) + env(safe-area-inset-bottom)); }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
90
|
+
.panel-inner {
|
|
91
|
+
transition: opacity 160ms ease, transform 160ms ease;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.header, .footer {
|
|
96
|
+
padding: var(--zz-modal-padding, 16px);
|
|
97
|
+
flex: 0 0 auto;
|
|
98
|
+
}
|
|
99
|
+
:host([no-header]) .header, :host([no-header-auto]) .header { display: none; padding: 0; }
|
|
100
|
+
:host([no-footer]) .footer, :host([no-footer-auto]) .footer { display: none; padding: 0; }
|
|
101
|
+
.body {
|
|
102
|
+
padding: var(--zz-modal-padding, 16px);
|
|
103
|
+
overflow: auto;
|
|
104
|
+
flex: 1 1 auto;
|
|
105
|
+
}
|
|
106
|
+
.close-btn {
|
|
107
|
+
all: unset;
|
|
108
|
+
cursor: pointer;
|
|
109
|
+
position: absolute;
|
|
110
|
+
top: 8px;
|
|
111
|
+
right: 8px;
|
|
112
|
+
width: 32px;
|
|
113
|
+
height: 32px;
|
|
114
|
+
border-radius: 9999px;
|
|
115
|
+
display: grid;
|
|
116
|
+
place-items: center;
|
|
117
|
+
}
|
|
118
|
+
.close-btn:focus-visible { outline: 2px solid #3b82f6; outline-offset: 2px; }
|
|
119
|
+
`;
|
|
120
|
+
|
|
121
|
+
const TEMPLATE = document.createElement('template');
|
|
122
|
+
TEMPLATE.innerHTML = `
|
|
123
|
+
<dialog part="dialog" aria-modal="true">
|
|
124
|
+
<div class="panel" part="backdrop">
|
|
125
|
+
<div class="panel-inner" role="document" part="panel">
|
|
126
|
+
<button style="z-index: 1000;" class="close-btn" part="close-button" aria-label="Close" data-zz-modal-close>
|
|
127
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
|
|
128
|
+
</button>
|
|
129
|
+
<header class="header" part="header"><slot name="header"></slot></header>
|
|
130
|
+
<section class="body" part="body"><slot></slot></section>
|
|
131
|
+
<footer class="footer" part="footer"><slot name="footer"></slot></footer>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
</dialog>
|
|
135
|
+
`;
|
|
136
|
+
|
|
137
|
+
class ZZModal extends HTMLElement {
|
|
138
|
+
static get observedAttributes() { return ['open']; }
|
|
139
|
+
|
|
140
|
+
constructor() {
|
|
141
|
+
super();
|
|
142
|
+
this._onKeydown = this._onKeydown.bind(this);
|
|
143
|
+
this._onNativeClose = this._onNativeClose.bind(this);
|
|
144
|
+
this._onMouseDown = this._onMouseDown.bind(this);
|
|
145
|
+
this._onClick = this._onClick.bind(this);
|
|
146
|
+
this._previousActive = null;
|
|
147
|
+
this._mouseDownInsidePanel = false;
|
|
148
|
+
|
|
149
|
+
const shadow = this.attachShadow({ mode: 'open' });
|
|
150
|
+
const style = document.createElement('style');
|
|
151
|
+
style.textContent = STYLE_TEXT;
|
|
152
|
+
shadow.appendChild(style);
|
|
153
|
+
shadow.appendChild(TEMPLATE.content.cloneNode(true));
|
|
154
|
+
|
|
155
|
+
this._dialog = shadow.querySelector('dialog');
|
|
156
|
+
this._panel = shadow.querySelector('.panel-inner');
|
|
157
|
+
this._closeBtn = shadow.querySelector('[data-zz-modal-close]');
|
|
158
|
+
this._slotHeader = shadow.querySelector('slot[name="header"]');
|
|
159
|
+
this._slotFooter = shadow.querySelector('slot[name="footer"]');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
connectedCallback() {
|
|
163
|
+
// Delegated internal events
|
|
164
|
+
this._closeBtn.addEventListener('click', () => this.hide());
|
|
165
|
+
this._dialog.addEventListener('close', this._onNativeClose);
|
|
166
|
+
this._dialog.addEventListener('mousedown', this._onMouseDown);
|
|
167
|
+
this._dialog.addEventListener('click', this._onClick);
|
|
168
|
+
|
|
169
|
+
// Reflect open state if attribute present at mount
|
|
170
|
+
if (this.hasAttribute('open')) {
|
|
171
|
+
// Defer to ensure upgraded before open
|
|
172
|
+
queueMicrotask(() => this.show());
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Auto hide empty header/footer
|
|
176
|
+
this._slotHeader.addEventListener('slotchange', () => this._updateSlotVisibility());
|
|
177
|
+
this._slotFooter.addEventListener('slotchange', () => this._updateSlotVisibility());
|
|
178
|
+
// Initial
|
|
179
|
+
this._updateSlotVisibility();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
disconnectedCallback() {
|
|
183
|
+
this._dialog.removeEventListener('close', this._onNativeClose);
|
|
184
|
+
this._dialog.removeEventListener('mousedown', this._onMouseDown);
|
|
185
|
+
this._dialog.removeEventListener('click', this._onClick);
|
|
186
|
+
document.removeEventListener('keydown', this._onKeydown);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
190
|
+
if (name === 'open' && oldValue !== newValue) {
|
|
191
|
+
if (this.hasAttribute('open')) {
|
|
192
|
+
this.show();
|
|
193
|
+
} else {
|
|
194
|
+
this.hide();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
get open() { return this._dialog?.open || this.hasAttribute('open'); }
|
|
200
|
+
set open(val) { if (val) this.setAttribute('open', ''); else this.removeAttribute('open'); }
|
|
201
|
+
|
|
202
|
+
show() { this._openInternal(true); }
|
|
203
|
+
hide() { this._closeInternal(true); }
|
|
204
|
+
toggle() { this.open ? this.hide() : this.show(); }
|
|
205
|
+
|
|
206
|
+
// Aliases
|
|
207
|
+
showModal() { this.show(); }
|
|
208
|
+
close() { this.hide(); }
|
|
209
|
+
|
|
210
|
+
_emit(name, detail = {}) {
|
|
211
|
+
this.dispatchEvent(new CustomEvent(name, { detail, bubbles: true, composed: true }));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
_openInternal(emit = false) {
|
|
215
|
+
if (this._dialog.open) return;
|
|
216
|
+
this._previousActive = document.activeElement;
|
|
217
|
+
|
|
218
|
+
// Scroll lock
|
|
219
|
+
if (!this.hasAttribute('no-scroll-lock')) {
|
|
220
|
+
this._lockScroll();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Inert others
|
|
224
|
+
if (this.hasAttribute('inert-others')) {
|
|
225
|
+
this._toggleInert(true);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Native dialog path
|
|
229
|
+
try {
|
|
230
|
+
if ('showModal' in HTMLDialogElement.prototype) {
|
|
231
|
+
this._dialog.showModal();
|
|
232
|
+
} else {
|
|
233
|
+
// Fallback
|
|
234
|
+
this._dialog.setAttribute('open', '');
|
|
235
|
+
}
|
|
236
|
+
} catch (_) {
|
|
237
|
+
this._dialog.setAttribute('open', '');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Sync attribute for CSS animations
|
|
241
|
+
this.setAttribute('open', '');
|
|
242
|
+
|
|
243
|
+
// Listeners
|
|
244
|
+
document.addEventListener('keydown', this._onKeydown);
|
|
245
|
+
|
|
246
|
+
// Focus first focusable within panel
|
|
247
|
+
this._focusInitial();
|
|
248
|
+
|
|
249
|
+
if (emit) this._emit('zz-modal:open');
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
_closeInternal(emit = false) {
|
|
253
|
+
if (!this._dialog.open && !this.hasAttribute('open')) return;
|
|
254
|
+
|
|
255
|
+
// Remove listeners
|
|
256
|
+
document.removeEventListener('keydown', this._onKeydown);
|
|
257
|
+
|
|
258
|
+
// Native dialog close
|
|
259
|
+
try {
|
|
260
|
+
if (this._dialog.open) this._dialog.close();
|
|
261
|
+
} catch (_) {
|
|
262
|
+
// ignore
|
|
263
|
+
}
|
|
264
|
+
this.removeAttribute('open');
|
|
265
|
+
|
|
266
|
+
// Restore scroll and inert
|
|
267
|
+
if (!this.hasAttribute('no-scroll-lock')) {
|
|
268
|
+
this._unlockScroll();
|
|
269
|
+
}
|
|
270
|
+
if (this.hasAttribute('inert-others')) {
|
|
271
|
+
this._toggleInert(false);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Restore focus
|
|
275
|
+
if (this._previousActive && typeof this._previousActive.focus === 'function') {
|
|
276
|
+
this._previousActive.focus({ preventScroll: true });
|
|
277
|
+
}
|
|
278
|
+
this._previousActive = null;
|
|
279
|
+
|
|
280
|
+
if (emit) this._emit('zz-modal:close');
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
_onKeydown(e) {
|
|
284
|
+
const escAllowed = !this.hasAttribute('no-esc-close');
|
|
285
|
+
if (e.key === 'Escape' && escAllowed) {
|
|
286
|
+
e.stopPropagation();
|
|
287
|
+
this.hide();
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Basic focus trap for fallback scenario
|
|
291
|
+
if (e.key === 'Tab' && !('showModal' in HTMLDialogElement.prototype)) {
|
|
292
|
+
const focusables = this._getFocusable();
|
|
293
|
+
if (focusables.length === 0) return;
|
|
294
|
+
const first = focusables[0];
|
|
295
|
+
const last = focusables[focusables.length - 1];
|
|
296
|
+
const active = this.shadowRoot.activeElement || document.activeElement;
|
|
297
|
+
if (e.shiftKey) {
|
|
298
|
+
if (active === first || !this.contains(active)) {
|
|
299
|
+
e.preventDefault();
|
|
300
|
+
last.focus();
|
|
301
|
+
}
|
|
302
|
+
} else {
|
|
303
|
+
if (active === last || !this.contains(active)) {
|
|
304
|
+
e.preventDefault();
|
|
305
|
+
first.focus();
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
_onNativeClose() {
|
|
312
|
+
// Ensure attribute sync and cleanup
|
|
313
|
+
this._closeInternal(true);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
_onMouseDown(e) {
|
|
317
|
+
// Track whether mousedown originated inside the panel to differentiate drags
|
|
318
|
+
const path = e.composedPath();
|
|
319
|
+
this._mouseDownInsidePanel = path.includes(this._panel);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
_onClick(e) {
|
|
323
|
+
const backdropClose = !this.hasAttribute('no-backdrop-close');
|
|
324
|
+
if (!backdropClose) return;
|
|
325
|
+
|
|
326
|
+
// If click ended outside panel and started outside panel -> treat as backdrop click
|
|
327
|
+
const path = e.composedPath();
|
|
328
|
+
const clickInsidePanel = path.includes(this._panel);
|
|
329
|
+
if (!clickInsidePanel && !this._mouseDownInsidePanel) {
|
|
330
|
+
this.hide();
|
|
331
|
+
}
|
|
332
|
+
this._mouseDownInsidePanel = false;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
_updateSlotVisibility() {
|
|
336
|
+
try {
|
|
337
|
+
const hasHeader = (this._slotHeader.assignedNodes({ flatten: true }).filter(n => n.nodeType === 1 || (n.nodeType === 3 && n.textContent.trim())).length) > 0;
|
|
338
|
+
const hasFooter = (this._slotFooter.assignedNodes({ flatten: true }).filter(n => n.nodeType === 1 || (n.nodeType === 3 && n.textContent.trim())).length) > 0;
|
|
339
|
+
if (!hasHeader) this.setAttribute('no-header-auto', ''); else this.removeAttribute('no-header-auto');
|
|
340
|
+
if (!hasFooter) this.setAttribute('no-footer-auto', ''); else this.removeAttribute('no-footer-auto');
|
|
341
|
+
} catch (_) {
|
|
342
|
+
// ignore
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
_getFocusable() {
|
|
347
|
+
const root = this.shadowRoot;
|
|
348
|
+
const within = root.querySelector('.panel-inner');
|
|
349
|
+
return Array.from(within.querySelectorAll([
|
|
350
|
+
'a[href]','area[href]','input:not([disabled])','select:not([disabled])','textarea:not([disabled])',
|
|
351
|
+
'button:not([disabled])','iframe','object','embed','[contenteditable]','[tabindex]:not([tabindex="-1"])'
|
|
352
|
+
].join(',')));
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
_focusInitial() {
|
|
356
|
+
const focusables = this._getFocusable();
|
|
357
|
+
if (focusables.length > 0) {
|
|
358
|
+
focusables[0].focus({ preventScroll: true });
|
|
359
|
+
} else {
|
|
360
|
+
// Focus the dialog so that ESC works consistently
|
|
361
|
+
this._dialog.focus({ preventScroll: true });
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
_lockScroll() {
|
|
366
|
+
const body = document.body;
|
|
367
|
+
this._prevBodyOverflow = body.style.overflow;
|
|
368
|
+
body.style.overflow = 'hidden';
|
|
369
|
+
body.setAttribute('data-zz-modal-open', '');
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
_unlockScroll() {
|
|
373
|
+
const body = document.body;
|
|
374
|
+
body.style.overflow = this._prevBodyOverflow || '';
|
|
375
|
+
body.removeAttribute('data-zz-modal-open');
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
_toggleInert(isInert) {
|
|
379
|
+
const toInert = [];
|
|
380
|
+
const root = document.body;
|
|
381
|
+
for (const child of Array.from(root.children)) {
|
|
382
|
+
if (child === this.parentElement) continue;
|
|
383
|
+
toInert.push(child);
|
|
384
|
+
}
|
|
385
|
+
toInert.forEach((el) => {
|
|
386
|
+
if (isInert) el.setAttribute('inert', ''); else el.removeAttribute('inert');
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
customElements.define('zz-modal', ZZModal);
|
|
392
|
+
|
|
393
|
+
// Global open/close triggers
|
|
394
|
+
document.addEventListener('click', (e) => {
|
|
395
|
+
const openTrigger = e.target.closest('[data-zz-modal-target]');
|
|
396
|
+
if (openTrigger) {
|
|
397
|
+
const selector = openTrigger.getAttribute('data-zz-modal-target');
|
|
398
|
+
if (selector) {
|
|
399
|
+
const modal = document.querySelector(selector);
|
|
400
|
+
if (modal && modal.show) {
|
|
401
|
+
e.preventDefault();
|
|
402
|
+
modal.show();
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
const closeTrigger = e.target.closest('[data-zz-modal-close]');
|
|
408
|
+
if (closeTrigger) {
|
|
409
|
+
const hostModal = closeTrigger.closest('zz-modal');
|
|
410
|
+
if (hostModal && hostModal.hide) {
|
|
411
|
+
e.preventDefault();
|
|
412
|
+
hostModal.hide();
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}, { capture: true });
|
|
416
|
+
})();
|
|
417
|
+
|
|
418
|
+
|
package/blocks/zz-button.liquid
CHANGED
|
@@ -9,6 +9,16 @@
|
|
|
9
9
|
"label": "按钮文字",
|
|
10
10
|
"default": "按钮"
|
|
11
11
|
},
|
|
12
|
+
{
|
|
13
|
+
"type": "text",
|
|
14
|
+
"id": "btn_id",
|
|
15
|
+
"label": "按钮id属性值",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"type": "text",
|
|
19
|
+
"id": "modal_id",
|
|
20
|
+
"label": "按钮触发modal的id",
|
|
21
|
+
},
|
|
12
22
|
{
|
|
13
23
|
"type": "select",
|
|
14
24
|
"id": "function_type",
|
|
@@ -188,7 +198,9 @@
|
|
|
188
198
|
icon_left_margin: block.settings.icon_left_margin,
|
|
189
199
|
icon_right_margin: block.settings.icon_right_margin,
|
|
190
200
|
width: block.settings.mobile_width,
|
|
191
|
-
class_name: btn_class
|
|
201
|
+
class_name: btn_class,
|
|
202
|
+
btn_id: block.settings.btn_id,
|
|
203
|
+
modal_id: block.settings.modal_id
|
|
192
204
|
%}
|
|
193
205
|
|
|
194
206
|
|