@qzsy/dsh-bridge 3.3.0 → 3.3.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.
- package/CHANGELOG.md +18 -0
- package/README.md +2 -0
- package/client/client.js +207 -18
- package/client/mobile.js +225 -22
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# 更新日志
|
|
2
2
|
|
|
3
|
+
## [v3.3.1] - 2026-11-02
|
|
4
|
+
|
|
5
|
+
### 🐛 修复
|
|
6
|
+
|
|
7
|
+
- **全面屏安全区**:菜单按钮、内容区、抽屉都按 `safe-area-inset-*` 让位,刘海 / 状态栏 / 底部 home indicator 不再遮挡;横屏左右安全区同样生效。
|
|
8
|
+
- **首次加载闪动**:侧栏首帧先无动画隐藏,再启用 280ms 过渡,页面加载时不再看到侧栏从 0 滑出。
|
|
9
|
+
|
|
10
|
+
### ✨ 改进
|
|
11
|
+
|
|
12
|
+
- **手势滑动**:支持左缘右滑打开、抽屉内左滑关闭;拖动时抽屉跟手、遮罩同步渐变,松手按距离/速度吸附。
|
|
13
|
+
- **手势防误触**:纵向滚动不触发抽屉;滑动后的合成点击会被吞掉,避免误开/误关会话。
|
|
14
|
+
|
|
15
|
+
### 🧪 验证
|
|
16
|
+
|
|
17
|
+
- 模拟 47px 顶部 / 34px 底部安全区:按钮 y=57、内容 padding-top=99px,抽屉 padding 47/34 全部生效。
|
|
18
|
+
- 模拟触摸:拖动中 `translateX=-150px`、松手打开;抽屉内左滑关闭;纵向滑动不误开;9/9 通过。
|
|
19
|
+
|
|
20
|
+
|
|
3
21
|
## [v3.3.0] - 2026-11-02
|
|
4
22
|
|
|
5
23
|
### ✨ 新增
|
package/README.md
CHANGED
|
@@ -47,6 +47,8 @@ dsh plugin --profile <name> add @qzsy/dsh-bridge
|
|
|
47
47
|
窄屏(≤ 767px)访问 DSH 时,插件会自动把左侧栏改成抽屉,不需要额外配置:
|
|
48
48
|
|
|
49
49
|
- 内容区占满屏宽,左上角「菜单」按钮打开会话 / 工作区 / 插件入口;
|
|
50
|
+
- 支持手势:左缘向右滑打开、抽屉内向左滑关闭,拖动时跟手,松手吸附;
|
|
51
|
+
- 全面屏自动避开安全区:刘海 / 状态栏 / 底部 home indicator 不遮挡内容;
|
|
50
52
|
- 点遮罩、点会话行、按 Esc 或转回桌面宽度都会自动收起;
|
|
51
53
|
- 桌面端保持 DSH 原生布局,不受影响。
|
|
52
54
|
|
package/client/client.js
CHANGED
|
@@ -220,27 +220,62 @@ var MOBILE_MAX_WIDTH = 767;
|
|
|
220
220
|
var MOBILE_DRAWER_CSS_ID = "qzsy-mobile-drawer-styles";
|
|
221
221
|
var MOBILE_UI_FLAG = "data-qzsy-mobile-drawer";
|
|
222
222
|
var OPEN_CLASS = "qzsy-drawer-open";
|
|
223
|
+
var DRAGGING_CLASS = "qzsy-drawer-dragging";
|
|
224
|
+
var HEADER_CLASS = "qzsy-mobile-header";
|
|
223
225
|
var MENU_CLASS = "qzsy-mobile-menu-btn";
|
|
224
226
|
var BACKDROP_CLASS = "qzsy-mobile-backdrop";
|
|
225
227
|
var STYLE_PLUGIN = "@qzsy/dsh-bridge";
|
|
228
|
+
var EDGE_START_ZONE = 28;
|
|
229
|
+
var DRAG_THRESHOLD = 8;
|
|
226
230
|
var MOBILE_DRAWER_CSS = `
|
|
227
|
-
|
|
231
|
+
:root {
|
|
232
|
+
--qzsy-safe-top: env(safe-area-inset-top, 0px);
|
|
233
|
+
--qzsy-safe-bottom: env(safe-area-inset-bottom, 0px);
|
|
234
|
+
--qzsy-safe-left: env(safe-area-inset-left, 0px);
|
|
235
|
+
--qzsy-safe-right: env(safe-area-inset-right, 0px);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.qzsy-mobile-header,
|
|
228
239
|
.qzsy-mobile-backdrop {
|
|
229
240
|
display: none;
|
|
230
241
|
}
|
|
231
242
|
|
|
243
|
+
/* \u83DC\u5355\u6309\u94AE\u5BB9\u5668\uFF1A\u59CB\u7EC8\u5728\u5BBF\u4E3B\u4E4B\u4E0A\uFF1B\u5BB9\u5668\u81EA\u8EAB\u4E0D\u62E6\u622A\u70B9\u51FB */
|
|
244
|
+
.qzsy-mobile-header {
|
|
245
|
+
position: fixed;
|
|
246
|
+
top: 0;
|
|
247
|
+
left: 0;
|
|
248
|
+
right: 0;
|
|
249
|
+
z-index: 65;
|
|
250
|
+
box-sizing: border-box;
|
|
251
|
+
align-items: flex-end;
|
|
252
|
+
padding-top: 10px;
|
|
253
|
+
padding-top: calc(10px + var(--qzsy-safe-top, 0px));
|
|
254
|
+
padding-right: 10px;
|
|
255
|
+
padding-right: calc(10px + var(--qzsy-safe-right, 0px));
|
|
256
|
+
padding-bottom: 2px;
|
|
257
|
+
padding-left: 10px;
|
|
258
|
+
padding-left: calc(10px + var(--qzsy-safe-left, 0px));
|
|
259
|
+
pointer-events: none;
|
|
260
|
+
}
|
|
261
|
+
|
|
232
262
|
@media (max-width: 767px) {
|
|
233
|
-
|
|
263
|
+
.qzsy-mobile-header { display: flex; }
|
|
264
|
+
|
|
265
|
+
/* 1. \u5DE6\u4FA7\u680F\u8131\u79BB grid\uFF0C\u5185\u5BB9\u5360\u6EE1\uFF1B\u9876\u90E8\u7ED9\u83DC\u5355\u6309\u94AE\u7559\u51FA\u5B89\u5168\u533A\u9AD8\u5EA6 */
|
|
234
266
|
[data-slot="root"] > div[class*="_frame"] {
|
|
235
267
|
grid-template-columns: 0px minmax(0px, 1fr) minmax(0px, 0px) !important;
|
|
236
|
-
padding-top: 52px !important;
|
|
237
268
|
box-sizing: border-box !important;
|
|
269
|
+
padding-top: 52px !important;
|
|
270
|
+
padding-top: calc(52px + var(--qzsy-safe-top, 0px)) !important;
|
|
271
|
+
padding-bottom: 0 !important;
|
|
272
|
+
padding-bottom: var(--qzsy-safe-bottom, 0px) !important;
|
|
238
273
|
}
|
|
239
274
|
div[class*="_centerCol"] { grid-column: 2 !important; }
|
|
240
275
|
div[class*="_rightbarCol"] { grid-column: 3 !important; }
|
|
241
276
|
div[class*="_overlayLayer"] { grid-column: 1 / -1 !important; }
|
|
242
277
|
|
|
243
|
-
/* 2. \u539F\u751F\u4FA7\u680F\u6539\u4E3A fixed \u62BD\u5C49\uFF08\u9ED8\u8BA4\u85CF\u5728\u5C4F\u5E55\u5DE6\u4FA7\uFF09 */
|
|
278
|
+
/* 2. \u539F\u751F\u4FA7\u680F\u6539\u4E3A fixed \u62BD\u5C49\uFF08\u9ED8\u8BA4\u85CF\u5728\u5C4F\u5E55\u5DE6\u4FA7\uFF1B\u5185\u90E8\u907F\u5F00\u5B89\u5168\u533A\uFF09 */
|
|
244
279
|
div[class*="_sidebarCol"] {
|
|
245
280
|
position: fixed !important;
|
|
246
281
|
top: 0 !important;
|
|
@@ -250,16 +285,26 @@ var MOBILE_DRAWER_CSS = `
|
|
|
250
285
|
max-width: 84vw !important;
|
|
251
286
|
height: 100vh !important;
|
|
252
287
|
height: 100dvh !important;
|
|
288
|
+
box-sizing: border-box !important;
|
|
289
|
+
padding-top: 0 !important;
|
|
290
|
+
padding-top: var(--qzsy-safe-top, 0px) !important;
|
|
291
|
+
padding-bottom: 0 !important;
|
|
292
|
+
padding-bottom: var(--qzsy-safe-bottom, 0px) !important;
|
|
293
|
+
padding-left: 0 !important;
|
|
294
|
+
padding-left: var(--qzsy-safe-left, 0px) !important;
|
|
253
295
|
z-index: 60 !important;
|
|
254
296
|
background: var(--dsw-alias-bg-layer-1, #ffffff) !important;
|
|
255
297
|
border-right: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.08)) !important;
|
|
256
298
|
box-shadow: none !important;
|
|
257
299
|
transform: translateX(-102%) !important;
|
|
258
|
-
transition: transform 280ms cubic-bezier(0.16, 1, 0.3, 1) !important;
|
|
259
300
|
overflow: hidden !important;
|
|
260
301
|
overscroll-behavior: contain;
|
|
261
302
|
will-change: transform;
|
|
262
303
|
}
|
|
304
|
+
/* \u9996\u5E27\u5148\u5C31\u4F4D\u3001\u4E0D\u64AD\u653E\u5165\u573A\u52A8\u753B\uFF0C\u907F\u514D\u9875\u9762\u52A0\u8F7D\u65F6\u4FA7\u680F\u4ECE 0 \u6ED1\u51FA */
|
|
305
|
+
body.qzsy-drawer-ready div[class*="_sidebarCol"] {
|
|
306
|
+
transition: transform 280ms cubic-bezier(0.16, 1, 0.3, 1) !important;
|
|
307
|
+
}
|
|
263
308
|
body.qzsy-drawer-open div[class*="_sidebarCol"] {
|
|
264
309
|
transform: translateX(0) !important;
|
|
265
310
|
box-shadow: 8px 0 32px rgba(0, 0, 0, 0.18) !important;
|
|
@@ -268,6 +313,17 @@ var MOBILE_DRAWER_CSS = `
|
|
|
268
313
|
width: 100% !important;
|
|
269
314
|
}
|
|
270
315
|
|
|
316
|
+
/* \u62D6\u52A8\u4E2D\uFF1A\u5173\u95ED\u8FC7\u6E21\uFF0Ctransform/\u906E\u7F69\u76F4\u63A5\u8DDF\u624B */
|
|
317
|
+
body.qzsy-drawer-dragging div[class*="_sidebarCol"] {
|
|
318
|
+
transition: none !important;
|
|
319
|
+
transform: translateX(var(--qzsy-drawer-x, -102%)) !important;
|
|
320
|
+
}
|
|
321
|
+
body.qzsy-drawer-dragging .qzsy-mobile-backdrop {
|
|
322
|
+
transition: none !important;
|
|
323
|
+
opacity: var(--qzsy-drawer-progress, 0) !important;
|
|
324
|
+
pointer-events: auto !important;
|
|
325
|
+
}
|
|
326
|
+
|
|
271
327
|
/* 3. \u534A\u900F\u660E\u906E\u7F69\uFF1A\u70B9\u51FB\u6536\u8D77 */
|
|
272
328
|
.qzsy-mobile-backdrop {
|
|
273
329
|
display: block;
|
|
@@ -286,17 +342,13 @@ var MOBILE_DRAWER_CSS = `
|
|
|
286
342
|
pointer-events: auto;
|
|
287
343
|
}
|
|
288
344
|
|
|
289
|
-
/* 4. \
|
|
345
|
+
/* 4. \u83DC\u5355\u6309\u94AE\uFF1A\u4F4D\u4E8E\u5B89\u5168\u533A\u4E0B\u65B9\u7684 52px \u7559\u767D\u6761\u5185 */
|
|
290
346
|
.qzsy-mobile-menu-btn {
|
|
291
347
|
display: inline-flex;
|
|
292
|
-
position: fixed;
|
|
293
|
-
top: 10px;
|
|
294
|
-
left: 10px;
|
|
295
|
-
width: 40px;
|
|
296
|
-
height: 40px;
|
|
297
|
-
z-index: 65;
|
|
298
348
|
align-items: center;
|
|
299
349
|
justify-content: center;
|
|
350
|
+
width: 40px;
|
|
351
|
+
height: 40px;
|
|
300
352
|
padding: 0;
|
|
301
353
|
border: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.1));
|
|
302
354
|
border-radius: 12px;
|
|
@@ -304,6 +356,7 @@ var MOBILE_DRAWER_CSS = `
|
|
|
304
356
|
color: var(--dsw-alias-label-primary, #111827);
|
|
305
357
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
|
|
306
358
|
cursor: pointer;
|
|
359
|
+
pointer-events: auto;
|
|
307
360
|
-webkit-tap-highlight-color: transparent;
|
|
308
361
|
transition: opacity 180ms ease, transform 180ms ease;
|
|
309
362
|
}
|
|
@@ -336,6 +389,10 @@ function injectStyles() {
|
|
|
336
389
|
}
|
|
337
390
|
if (style.textContent !== MOBILE_DRAWER_CSS) style.textContent = MOBILE_DRAWER_CSS;
|
|
338
391
|
}
|
|
392
|
+
function drawerWidth() {
|
|
393
|
+
if (typeof window === "undefined") return 300;
|
|
394
|
+
return Math.min(300, Math.round(window.innerWidth * 0.84));
|
|
395
|
+
}
|
|
339
396
|
function setupMobileDrawer() {
|
|
340
397
|
if (typeof document === "undefined" || typeof window === "undefined") return () => {
|
|
341
398
|
};
|
|
@@ -346,16 +403,24 @@ function setupMobileDrawer() {
|
|
|
346
403
|
let menuBtn = null;
|
|
347
404
|
let observer = null;
|
|
348
405
|
let disposed = false;
|
|
406
|
+
let gesture = null;
|
|
407
|
+
let suppressClickUntil = 0;
|
|
349
408
|
const ensureElements = () => {
|
|
350
409
|
if (disposed || !document.body) return;
|
|
351
410
|
injectStyles();
|
|
352
|
-
let
|
|
411
|
+
let header = document.querySelector(`.${HEADER_CLASS}`);
|
|
412
|
+
if (!header) {
|
|
413
|
+
header = document.createElement("div");
|
|
414
|
+
header.className = HEADER_CLASS;
|
|
415
|
+
document.body.appendChild(header);
|
|
416
|
+
}
|
|
417
|
+
let btn = header.querySelector(`.${MENU_CLASS}`);
|
|
353
418
|
if (!btn) {
|
|
354
419
|
btn = document.createElement("button");
|
|
355
420
|
btn.type = "button";
|
|
356
421
|
btn.className = MENU_CLASS;
|
|
357
422
|
btn.innerHTML = '<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><line x1="3" y1="8" x2="21" y2="8"></line><line x1="3" y1="15" x2="15" y2="15"></line></svg>';
|
|
358
|
-
|
|
423
|
+
header.appendChild(btn);
|
|
359
424
|
}
|
|
360
425
|
let mask = document.querySelector(`.${BACKDROP_CLASS}`);
|
|
361
426
|
if (!mask) {
|
|
@@ -386,6 +451,15 @@ function setupMobileDrawer() {
|
|
|
386
451
|
}
|
|
387
452
|
}
|
|
388
453
|
};
|
|
454
|
+
const setDragProgress = (x, width) => {
|
|
455
|
+
document.body.style.setProperty("--qzsy-drawer-x", `${x}px`);
|
|
456
|
+
document.body.style.setProperty("--qzsy-drawer-progress", String(Math.max(0, Math.min(1, 1 - Math.abs(x) / width))));
|
|
457
|
+
};
|
|
458
|
+
const clearDrag = () => {
|
|
459
|
+
document.body.classList.remove(DRAGGING_CLASS);
|
|
460
|
+
document.body.style.removeProperty("--qzsy-drawer-x");
|
|
461
|
+
document.body.style.removeProperty("--qzsy-drawer-progress");
|
|
462
|
+
};
|
|
389
463
|
const openDrawer = () => {
|
|
390
464
|
if (disposed || !isMobileViewport() || !document.body) return;
|
|
391
465
|
ensureElements();
|
|
@@ -411,6 +485,11 @@ function setupMobileDrawer() {
|
|
|
411
485
|
}, 60);
|
|
412
486
|
};
|
|
413
487
|
const onClick = (event) => {
|
|
488
|
+
if (Date.now() < suppressClickUntil) {
|
|
489
|
+
event.preventDefault();
|
|
490
|
+
event.stopPropagation();
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
414
493
|
const target = event.target;
|
|
415
494
|
if (!isElement(target)) return;
|
|
416
495
|
if (target.closest(`.${MENU_CLASS}`)) {
|
|
@@ -447,10 +526,106 @@ function setupMobileDrawer() {
|
|
|
447
526
|
if (event.key === "Escape" && isOpen()) closeDrawer();
|
|
448
527
|
};
|
|
449
528
|
const onResize = () => {
|
|
450
|
-
if (!isMobileViewport() && isOpen())
|
|
529
|
+
if (!isMobileViewport() && isOpen()) {
|
|
530
|
+
gesture = null;
|
|
531
|
+
clearDrag();
|
|
532
|
+
closeDrawer({ restoreFocus: false });
|
|
533
|
+
}
|
|
451
534
|
};
|
|
452
535
|
const onVisibilityChange = () => {
|
|
453
|
-
if (document.visibilityState === "hidden" && isOpen())
|
|
536
|
+
if (document.visibilityState === "hidden" && isOpen()) {
|
|
537
|
+
gesture = null;
|
|
538
|
+
clearDrag();
|
|
539
|
+
closeDrawer({ restoreFocus: false });
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
const onTouchStart = (event) => {
|
|
543
|
+
if (disposed || !isMobileViewport() || event.touches?.length !== 1) return;
|
|
544
|
+
const touch = event.touches[0];
|
|
545
|
+
const sidebar = document.querySelector('div[class*="_sidebarCol"]');
|
|
546
|
+
if (!sidebar) return;
|
|
547
|
+
const open = isOpen();
|
|
548
|
+
if (!open) {
|
|
549
|
+
if (touch.clientX > EDGE_START_ZONE) return;
|
|
550
|
+
ensureSidebarExpanded();
|
|
551
|
+
gesture = {
|
|
552
|
+
mode: "open",
|
|
553
|
+
startX: touch.clientX,
|
|
554
|
+
startY: touch.clientY,
|
|
555
|
+
startT: Date.now(),
|
|
556
|
+
width: drawerWidth(),
|
|
557
|
+
active: false,
|
|
558
|
+
dx: 0
|
|
559
|
+
};
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
if (!sidebar.contains(event.target)) return;
|
|
563
|
+
if (event.target.closest?.('input, textarea, select, [contenteditable="true"]')) return;
|
|
564
|
+
gesture = {
|
|
565
|
+
mode: "close",
|
|
566
|
+
startX: touch.clientX,
|
|
567
|
+
startY: touch.clientY,
|
|
568
|
+
startT: Date.now(),
|
|
569
|
+
width: drawerWidth(),
|
|
570
|
+
active: false,
|
|
571
|
+
dx: 0
|
|
572
|
+
};
|
|
573
|
+
};
|
|
574
|
+
const onTouchMove = (event) => {
|
|
575
|
+
if (!gesture || disposed) return;
|
|
576
|
+
const touch = event.touches?.[0];
|
|
577
|
+
if (!touch) return;
|
|
578
|
+
const dx = touch.clientX - gesture.startX;
|
|
579
|
+
const dy = touch.clientY - gesture.startY;
|
|
580
|
+
if (!gesture.active) {
|
|
581
|
+
if (Math.abs(dx) < DRAG_THRESHOLD) return;
|
|
582
|
+
if (Math.abs(dx) < Math.abs(dy) * 1.2) {
|
|
583
|
+
gesture = null;
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
if (gesture.mode === "open" && dx <= 0) {
|
|
587
|
+
gesture = null;
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
if (gesture.mode === "close" && dx >= 0) {
|
|
591
|
+
gesture = null;
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
gesture.active = true;
|
|
595
|
+
suppressClickUntil = Date.now() + 450;
|
|
596
|
+
document.body.classList.add(DRAGGING_CLASS);
|
|
597
|
+
}
|
|
598
|
+
const width = Math.max(1, gesture.width);
|
|
599
|
+
let x;
|
|
600
|
+
if (gesture.mode === "open") x = Math.max(-width, Math.min(0, -width + dx));
|
|
601
|
+
else x = Math.max(-width, Math.min(0, dx));
|
|
602
|
+
gesture.dx = dx;
|
|
603
|
+
setDragProgress(x, width);
|
|
604
|
+
};
|
|
605
|
+
const finishGesture = (touch) => {
|
|
606
|
+
if (!gesture) return;
|
|
607
|
+
const current = gesture;
|
|
608
|
+
gesture = null;
|
|
609
|
+
if (!current.active) return;
|
|
610
|
+
const endX = touch ? touch.clientX : current.startX + current.dx;
|
|
611
|
+
const dx = endX - current.startX;
|
|
612
|
+
const elapsed = Math.max(1, Date.now() - current.startT);
|
|
613
|
+
const velocity = dx / elapsed;
|
|
614
|
+
const threshold = Math.min(90, Math.max(60, current.width * 0.3));
|
|
615
|
+
const openNow = current.mode === "open" ? dx > threshold || velocity > 0.45 : !(dx < -Math.max(50, threshold * 0.7) || velocity < -0.35);
|
|
616
|
+
document.body.classList.toggle(OPEN_CLASS, openNow);
|
|
617
|
+
if (openNow) ensureSidebarExpanded();
|
|
618
|
+
syncAria();
|
|
619
|
+
suppressClickUntil = Date.now() + 250;
|
|
620
|
+
window.requestAnimationFrame(clearDrag);
|
|
621
|
+
};
|
|
622
|
+
const onTouchEnd = (event) => {
|
|
623
|
+
if (!gesture) return;
|
|
624
|
+
finishGesture(event.changedTouches?.[0]);
|
|
625
|
+
};
|
|
626
|
+
const onTouchCancel = () => {
|
|
627
|
+
if (!gesture) return;
|
|
628
|
+
finishGesture(null);
|
|
454
629
|
};
|
|
455
630
|
injectStyles();
|
|
456
631
|
ensureElements();
|
|
@@ -458,9 +633,16 @@ function setupMobileDrawer() {
|
|
|
458
633
|
document.addEventListener("keydown", onKeyDown, true);
|
|
459
634
|
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
460
635
|
window.addEventListener("resize", onResize);
|
|
636
|
+
window.addEventListener("touchstart", onTouchStart, { passive: true });
|
|
637
|
+
window.addEventListener("touchmove", onTouchMove, { passive: true });
|
|
638
|
+
window.addEventListener("touchend", onTouchEnd, { passive: true });
|
|
639
|
+
window.addEventListener("touchcancel", onTouchCancel, { passive: true });
|
|
640
|
+
window.requestAnimationFrame(() => {
|
|
641
|
+
if (!disposed && document.body) document.body.classList.add("qzsy-drawer-ready");
|
|
642
|
+
});
|
|
461
643
|
if (typeof MutationObserver === "function" && document.body) {
|
|
462
644
|
observer = new MutationObserver(() => {
|
|
463
|
-
if (!document.querySelector(`.${MENU_CLASS}`) || !document.querySelector(`.${BACKDROP_CLASS}`) || !document.getElementById(MOBILE_DRAWER_CSS_ID)) {
|
|
645
|
+
if (!document.querySelector(`.${HEADER_CLASS}`) || !document.querySelector(`.${MENU_CLASS}`) || !document.querySelector(`.${BACKDROP_CLASS}`) || !document.getElementById(MOBILE_DRAWER_CSS_ID)) {
|
|
464
646
|
ensureElements();
|
|
465
647
|
}
|
|
466
648
|
});
|
|
@@ -469,6 +651,7 @@ function setupMobileDrawer() {
|
|
|
469
651
|
syncAria();
|
|
470
652
|
return () => {
|
|
471
653
|
disposed = true;
|
|
654
|
+
gesture = null;
|
|
472
655
|
try {
|
|
473
656
|
observer?.disconnect();
|
|
474
657
|
} catch {
|
|
@@ -477,8 +660,14 @@ function setupMobileDrawer() {
|
|
|
477
660
|
document.removeEventListener("keydown", onKeyDown, true);
|
|
478
661
|
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
479
662
|
window.removeEventListener("resize", onResize);
|
|
663
|
+
window.removeEventListener("touchstart", onTouchStart);
|
|
664
|
+
window.removeEventListener("touchmove", onTouchMove);
|
|
665
|
+
window.removeEventListener("touchend", onTouchEnd);
|
|
666
|
+
window.removeEventListener("touchcancel", onTouchCancel);
|
|
667
|
+
clearDrag();
|
|
480
668
|
document.body?.classList.remove(OPEN_CLASS);
|
|
481
|
-
document.
|
|
669
|
+
document.body?.classList.remove("qzsy-drawer-ready");
|
|
670
|
+
document.querySelector(`.${HEADER_CLASS}`)?.remove();
|
|
482
671
|
document.querySelector(`.${BACKDROP_CLASS}`)?.remove();
|
|
483
672
|
document.getElementById(MOBILE_DRAWER_CSS_ID)?.remove();
|
|
484
673
|
root.removeAttribute(MOBILE_UI_FLAG);
|
package/client/mobile.js
CHANGED
|
@@ -1,42 +1,78 @@
|
|
|
1
1
|
// 移动端抽屉导航:把宿主左侧栏(会话/工作区菜单)改成从左侧滑出的 sheet。
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
3
|
+
// 设计目标(对齐 tmp/dsh-bridge 的移动端体验,但只保留本项目需要的部分):
|
|
4
|
+
// - 窄屏内容占满屏宽,左侧栏变成带遮罩的抽屉;
|
|
5
|
+
// - 支持全面屏安全区(刘海/状态栏/home indicator),菜单按钮和抽屉内容都不被遮挡;
|
|
6
|
+
// - 支持手势:左缘右滑打开、抽屉内左滑关闭,拖动时抽屉跟随手指,松手吸附;
|
|
7
|
+
// - 桌面端完全不受影响。
|
|
6
8
|
//
|
|
7
9
|
// 宿主 DOM 是 CSS Modules 哈希类名,本模块只依赖稳定结构/ARIA:
|
|
8
10
|
// div[class*="_frame"] / _sidebarCol / _centerCol / _rightbarCol / _overlayLayer
|
|
9
11
|
// button[aria-label*="打开侧边栏"](英文 Open sidebar)
|
|
10
12
|
// div[class*="sessionRow"](会话行;projectRow 是工作区分组,不能误关)
|
|
11
|
-
// 不写死任何宿主哈希,宿主升级换前缀时选择器仍有效。
|
|
12
13
|
import { createTranslator } from './locales.mjs';
|
|
13
14
|
|
|
14
15
|
export const MOBILE_MAX_WIDTH = 767;
|
|
15
16
|
export const MOBILE_DRAWER_CSS_ID = 'qzsy-mobile-drawer-styles';
|
|
16
17
|
export const MOBILE_UI_FLAG = 'data-qzsy-mobile-drawer';
|
|
17
18
|
const OPEN_CLASS = 'qzsy-drawer-open';
|
|
19
|
+
const DRAGGING_CLASS = 'qzsy-drawer-dragging';
|
|
20
|
+
const HEADER_CLASS = 'qzsy-mobile-header';
|
|
18
21
|
const MENU_CLASS = 'qzsy-mobile-menu-btn';
|
|
19
22
|
const BACKDROP_CLASS = 'qzsy-mobile-backdrop';
|
|
20
23
|
const STYLE_PLUGIN = '@qzsy/dsh-bridge';
|
|
24
|
+
const EDGE_START_ZONE = 28;
|
|
25
|
+
const DRAG_THRESHOLD = 8;
|
|
21
26
|
|
|
22
27
|
export const MOBILE_DRAWER_CSS = `
|
|
23
|
-
|
|
28
|
+
:root {
|
|
29
|
+
--qzsy-safe-top: env(safe-area-inset-top, 0px);
|
|
30
|
+
--qzsy-safe-bottom: env(safe-area-inset-bottom, 0px);
|
|
31
|
+
--qzsy-safe-left: env(safe-area-inset-left, 0px);
|
|
32
|
+
--qzsy-safe-right: env(safe-area-inset-right, 0px);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.qzsy-mobile-header,
|
|
24
36
|
.qzsy-mobile-backdrop {
|
|
25
37
|
display: none;
|
|
26
38
|
}
|
|
27
39
|
|
|
40
|
+
/* 菜单按钮容器:始终在宿主之上;容器自身不拦截点击 */
|
|
41
|
+
.qzsy-mobile-header {
|
|
42
|
+
position: fixed;
|
|
43
|
+
top: 0;
|
|
44
|
+
left: 0;
|
|
45
|
+
right: 0;
|
|
46
|
+
z-index: 65;
|
|
47
|
+
box-sizing: border-box;
|
|
48
|
+
align-items: flex-end;
|
|
49
|
+
padding-top: 10px;
|
|
50
|
+
padding-top: calc(10px + var(--qzsy-safe-top, 0px));
|
|
51
|
+
padding-right: 10px;
|
|
52
|
+
padding-right: calc(10px + var(--qzsy-safe-right, 0px));
|
|
53
|
+
padding-bottom: 2px;
|
|
54
|
+
padding-left: 10px;
|
|
55
|
+
padding-left: calc(10px + var(--qzsy-safe-left, 0px));
|
|
56
|
+
pointer-events: none;
|
|
57
|
+
}
|
|
58
|
+
|
|
28
59
|
@media (max-width: 767px) {
|
|
29
|
-
|
|
60
|
+
.qzsy-mobile-header { display: flex; }
|
|
61
|
+
|
|
62
|
+
/* 1. 左侧栏脱离 grid,内容占满;顶部给菜单按钮留出安全区高度 */
|
|
30
63
|
[data-slot="root"] > div[class*="_frame"] {
|
|
31
64
|
grid-template-columns: 0px minmax(0px, 1fr) minmax(0px, 0px) !important;
|
|
32
|
-
padding-top: 52px !important;
|
|
33
65
|
box-sizing: border-box !important;
|
|
66
|
+
padding-top: 52px !important;
|
|
67
|
+
padding-top: calc(52px + var(--qzsy-safe-top, 0px)) !important;
|
|
68
|
+
padding-bottom: 0 !important;
|
|
69
|
+
padding-bottom: var(--qzsy-safe-bottom, 0px) !important;
|
|
34
70
|
}
|
|
35
71
|
div[class*="_centerCol"] { grid-column: 2 !important; }
|
|
36
72
|
div[class*="_rightbarCol"] { grid-column: 3 !important; }
|
|
37
73
|
div[class*="_overlayLayer"] { grid-column: 1 / -1 !important; }
|
|
38
74
|
|
|
39
|
-
/* 2. 原生侧栏改为 fixed
|
|
75
|
+
/* 2. 原生侧栏改为 fixed 抽屉(默认藏在屏幕左侧;内部避开安全区) */
|
|
40
76
|
div[class*="_sidebarCol"] {
|
|
41
77
|
position: fixed !important;
|
|
42
78
|
top: 0 !important;
|
|
@@ -46,16 +82,26 @@ export const MOBILE_DRAWER_CSS = `
|
|
|
46
82
|
max-width: 84vw !important;
|
|
47
83
|
height: 100vh !important;
|
|
48
84
|
height: 100dvh !important;
|
|
85
|
+
box-sizing: border-box !important;
|
|
86
|
+
padding-top: 0 !important;
|
|
87
|
+
padding-top: var(--qzsy-safe-top, 0px) !important;
|
|
88
|
+
padding-bottom: 0 !important;
|
|
89
|
+
padding-bottom: var(--qzsy-safe-bottom, 0px) !important;
|
|
90
|
+
padding-left: 0 !important;
|
|
91
|
+
padding-left: var(--qzsy-safe-left, 0px) !important;
|
|
49
92
|
z-index: 60 !important;
|
|
50
93
|
background: var(--dsw-alias-bg-layer-1, #ffffff) !important;
|
|
51
94
|
border-right: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.08)) !important;
|
|
52
95
|
box-shadow: none !important;
|
|
53
96
|
transform: translateX(-102%) !important;
|
|
54
|
-
transition: transform 280ms cubic-bezier(0.16, 1, 0.3, 1) !important;
|
|
55
97
|
overflow: hidden !important;
|
|
56
98
|
overscroll-behavior: contain;
|
|
57
99
|
will-change: transform;
|
|
58
100
|
}
|
|
101
|
+
/* 首帧先就位、不播放入场动画,避免页面加载时侧栏从 0 滑出 */
|
|
102
|
+
body.qzsy-drawer-ready div[class*="_sidebarCol"] {
|
|
103
|
+
transition: transform 280ms cubic-bezier(0.16, 1, 0.3, 1) !important;
|
|
104
|
+
}
|
|
59
105
|
body.qzsy-drawer-open div[class*="_sidebarCol"] {
|
|
60
106
|
transform: translateX(0) !important;
|
|
61
107
|
box-shadow: 8px 0 32px rgba(0, 0, 0, 0.18) !important;
|
|
@@ -64,6 +110,17 @@ export const MOBILE_DRAWER_CSS = `
|
|
|
64
110
|
width: 100% !important;
|
|
65
111
|
}
|
|
66
112
|
|
|
113
|
+
/* 拖动中:关闭过渡,transform/遮罩直接跟手 */
|
|
114
|
+
body.qzsy-drawer-dragging div[class*="_sidebarCol"] {
|
|
115
|
+
transition: none !important;
|
|
116
|
+
transform: translateX(var(--qzsy-drawer-x, -102%)) !important;
|
|
117
|
+
}
|
|
118
|
+
body.qzsy-drawer-dragging .qzsy-mobile-backdrop {
|
|
119
|
+
transition: none !important;
|
|
120
|
+
opacity: var(--qzsy-drawer-progress, 0) !important;
|
|
121
|
+
pointer-events: auto !important;
|
|
122
|
+
}
|
|
123
|
+
|
|
67
124
|
/* 3. 半透明遮罩:点击收起 */
|
|
68
125
|
.qzsy-mobile-backdrop {
|
|
69
126
|
display: block;
|
|
@@ -82,17 +139,13 @@ export const MOBILE_DRAWER_CSS = `
|
|
|
82
139
|
pointer-events: auto;
|
|
83
140
|
}
|
|
84
141
|
|
|
85
|
-
/* 4.
|
|
142
|
+
/* 4. 菜单按钮:位于安全区下方的 52px 留白条内 */
|
|
86
143
|
.qzsy-mobile-menu-btn {
|
|
87
144
|
display: inline-flex;
|
|
88
|
-
position: fixed;
|
|
89
|
-
top: 10px;
|
|
90
|
-
left: 10px;
|
|
91
|
-
width: 40px;
|
|
92
|
-
height: 40px;
|
|
93
|
-
z-index: 65;
|
|
94
145
|
align-items: center;
|
|
95
146
|
justify-content: center;
|
|
147
|
+
width: 40px;
|
|
148
|
+
height: 40px;
|
|
96
149
|
padding: 0;
|
|
97
150
|
border: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.1));
|
|
98
151
|
border-radius: 12px;
|
|
@@ -100,6 +153,7 @@ export const MOBILE_DRAWER_CSS = `
|
|
|
100
153
|
color: var(--dsw-alias-label-primary, #111827);
|
|
101
154
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
|
|
102
155
|
cursor: pointer;
|
|
156
|
+
pointer-events: auto;
|
|
103
157
|
-webkit-tap-highlight-color: transparent;
|
|
104
158
|
transition: opacity 180ms ease, transform 180ms ease;
|
|
105
159
|
}
|
|
@@ -137,6 +191,11 @@ function injectStyles() {
|
|
|
137
191
|
if (style.textContent !== MOBILE_DRAWER_CSS) style.textContent = MOBILE_DRAWER_CSS;
|
|
138
192
|
}
|
|
139
193
|
|
|
194
|
+
function drawerWidth() {
|
|
195
|
+
if (typeof window === 'undefined') return 300;
|
|
196
|
+
return Math.min(300, Math.round(window.innerWidth * 0.84));
|
|
197
|
+
}
|
|
198
|
+
|
|
140
199
|
/**
|
|
141
200
|
* 安装移动端抽屉导航。无 DOM 环境(单测/SSR)安全退化为 no-op。
|
|
142
201
|
* 返回 disposer;宿主若支持 ctx.effect,可注册为插件副作用。
|
|
@@ -150,18 +209,26 @@ export function setupMobileDrawer() {
|
|
|
150
209
|
let menuBtn = null;
|
|
151
210
|
let observer = null;
|
|
152
211
|
let disposed = false;
|
|
212
|
+
let gesture = null;
|
|
213
|
+
let suppressClickUntil = 0;
|
|
153
214
|
|
|
154
215
|
const ensureElements = () => {
|
|
155
216
|
if (disposed || !document.body) return;
|
|
156
217
|
injectStyles();
|
|
157
218
|
|
|
158
|
-
let
|
|
219
|
+
let header = document.querySelector(`.${HEADER_CLASS}`);
|
|
220
|
+
if (!header) {
|
|
221
|
+
header = document.createElement('div');
|
|
222
|
+
header.className = HEADER_CLASS;
|
|
223
|
+
document.body.appendChild(header);
|
|
224
|
+
}
|
|
225
|
+
let btn = header.querySelector(`.${MENU_CLASS}`);
|
|
159
226
|
if (!btn) {
|
|
160
227
|
btn = document.createElement('button');
|
|
161
228
|
btn.type = 'button';
|
|
162
229
|
btn.className = MENU_CLASS;
|
|
163
230
|
btn.innerHTML = '<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><line x1="3" y1="8" x2="21" y2="8"></line><line x1="3" y1="15" x2="15" y2="15"></line></svg>';
|
|
164
|
-
|
|
231
|
+
header.appendChild(btn);
|
|
165
232
|
}
|
|
166
233
|
let mask = document.querySelector(`.${BACKDROP_CLASS}`);
|
|
167
234
|
if (!mask) {
|
|
@@ -194,6 +261,17 @@ export function setupMobileDrawer() {
|
|
|
194
261
|
}
|
|
195
262
|
};
|
|
196
263
|
|
|
264
|
+
const setDragProgress = (x, width) => {
|
|
265
|
+
document.body.style.setProperty('--qzsy-drawer-x', `${x}px`);
|
|
266
|
+
document.body.style.setProperty('--qzsy-drawer-progress', String(Math.max(0, Math.min(1, 1 - Math.abs(x) / width))));
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const clearDrag = () => {
|
|
270
|
+
document.body.classList.remove(DRAGGING_CLASS);
|
|
271
|
+
document.body.style.removeProperty('--qzsy-drawer-x');
|
|
272
|
+
document.body.style.removeProperty('--qzsy-drawer-progress');
|
|
273
|
+
};
|
|
274
|
+
|
|
197
275
|
const openDrawer = () => {
|
|
198
276
|
if (disposed || !isMobileViewport() || !document.body) return;
|
|
199
277
|
ensureElements();
|
|
@@ -219,6 +297,11 @@ export function setupMobileDrawer() {
|
|
|
219
297
|
};
|
|
220
298
|
|
|
221
299
|
const onClick = (event) => {
|
|
300
|
+
if (Date.now() < suppressClickUntil) {
|
|
301
|
+
event.preventDefault();
|
|
302
|
+
event.stopPropagation();
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
222
305
|
const target = event.target;
|
|
223
306
|
if (!isElement(target)) return;
|
|
224
307
|
|
|
@@ -265,11 +348,115 @@ export function setupMobileDrawer() {
|
|
|
265
348
|
};
|
|
266
349
|
|
|
267
350
|
const onResize = () => {
|
|
268
|
-
if (!isMobileViewport() && isOpen())
|
|
351
|
+
if (!isMobileViewport() && isOpen()) {
|
|
352
|
+
gesture = null;
|
|
353
|
+
clearDrag();
|
|
354
|
+
closeDrawer({ restoreFocus: false });
|
|
355
|
+
}
|
|
269
356
|
};
|
|
270
357
|
|
|
271
358
|
const onVisibilityChange = () => {
|
|
272
|
-
if (document.visibilityState === 'hidden' && isOpen())
|
|
359
|
+
if (document.visibilityState === 'hidden' && isOpen()) {
|
|
360
|
+
gesture = null;
|
|
361
|
+
clearDrag();
|
|
362
|
+
closeDrawer({ restoreFocus: false });
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
// ---- 手势:左缘右滑打开;抽屉内左滑关闭;拖动跟手 ----
|
|
367
|
+
const onTouchStart = (event) => {
|
|
368
|
+
if (disposed || !isMobileViewport() || event.touches?.length !== 1) return;
|
|
369
|
+
const touch = event.touches[0];
|
|
370
|
+
const sidebar = document.querySelector('div[class*="_sidebarCol"]');
|
|
371
|
+
if (!sidebar) return;
|
|
372
|
+
const open = isOpen();
|
|
373
|
+
|
|
374
|
+
if (!open) {
|
|
375
|
+
if (touch.clientX > EDGE_START_ZONE) return;
|
|
376
|
+
// 先把宿主侧栏展开,保证拖动时抽屉里有完整会话列表
|
|
377
|
+
ensureSidebarExpanded();
|
|
378
|
+
gesture = {
|
|
379
|
+
mode: 'open',
|
|
380
|
+
startX: touch.clientX,
|
|
381
|
+
startY: touch.clientY,
|
|
382
|
+
startT: Date.now(),
|
|
383
|
+
width: drawerWidth(),
|
|
384
|
+
active: false,
|
|
385
|
+
dx: 0,
|
|
386
|
+
};
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (!sidebar.contains(event.target)) return;
|
|
391
|
+
if (event.target.closest?.('input, textarea, select, [contenteditable="true"]')) return;
|
|
392
|
+
gesture = {
|
|
393
|
+
mode: 'close',
|
|
394
|
+
startX: touch.clientX,
|
|
395
|
+
startY: touch.clientY,
|
|
396
|
+
startT: Date.now(),
|
|
397
|
+
width: drawerWidth(),
|
|
398
|
+
active: false,
|
|
399
|
+
dx: 0,
|
|
400
|
+
};
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
const onTouchMove = (event) => {
|
|
404
|
+
if (!gesture || disposed) return;
|
|
405
|
+
const touch = event.touches?.[0];
|
|
406
|
+
if (!touch) return;
|
|
407
|
+
const dx = touch.clientX - gesture.startX;
|
|
408
|
+
const dy = touch.clientY - gesture.startY;
|
|
409
|
+
|
|
410
|
+
if (!gesture.active) {
|
|
411
|
+
if (Math.abs(dx) < DRAG_THRESHOLD) return;
|
|
412
|
+
// 纵向为主:交还给宿主滚动,本次不拦截
|
|
413
|
+
if (Math.abs(dx) < Math.abs(dy) * 1.2) { gesture = null; return; }
|
|
414
|
+
if (gesture.mode === 'open' && dx <= 0) { gesture = null; return; }
|
|
415
|
+
if (gesture.mode === 'close' && dx >= 0) { gesture = null; return; }
|
|
416
|
+
gesture.active = true;
|
|
417
|
+
suppressClickUntil = Date.now() + 450;
|
|
418
|
+
document.body.classList.add(DRAGGING_CLASS);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const width = Math.max(1, gesture.width);
|
|
422
|
+
let x;
|
|
423
|
+
if (gesture.mode === 'open') x = Math.max(-width, Math.min(0, -width + dx));
|
|
424
|
+
else x = Math.max(-width, Math.min(0, dx));
|
|
425
|
+
gesture.dx = dx;
|
|
426
|
+
setDragProgress(x, width);
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
const finishGesture = (touch) => {
|
|
430
|
+
if (!gesture) return;
|
|
431
|
+
const current = gesture;
|
|
432
|
+
gesture = null;
|
|
433
|
+
if (!current.active) return;
|
|
434
|
+
|
|
435
|
+
const endX = touch ? touch.clientX : current.startX + current.dx;
|
|
436
|
+
const dx = endX - current.startX;
|
|
437
|
+
const elapsed = Math.max(1, Date.now() - current.startT);
|
|
438
|
+
const velocity = dx / elapsed;
|
|
439
|
+
const threshold = Math.min(90, Math.max(60, current.width * 0.3));
|
|
440
|
+
const openNow = current.mode === 'open'
|
|
441
|
+
? dx > threshold || velocity > 0.45
|
|
442
|
+
: !(dx < -Math.max(50, threshold * 0.7) || velocity < -0.35);
|
|
443
|
+
|
|
444
|
+
document.body.classList.toggle(OPEN_CLASS, openNow);
|
|
445
|
+
if (openNow) ensureSidebarExpanded();
|
|
446
|
+
syncAria();
|
|
447
|
+
suppressClickUntil = Date.now() + 250;
|
|
448
|
+
// 等一帧让浏览器接住当前拖动位置,再撤掉拖动样式做吸附动画
|
|
449
|
+
window.requestAnimationFrame(clearDrag);
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
const onTouchEnd = (event) => {
|
|
453
|
+
if (!gesture) return;
|
|
454
|
+
finishGesture(event.changedTouches?.[0]);
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
const onTouchCancel = () => {
|
|
458
|
+
if (!gesture) return;
|
|
459
|
+
finishGesture(null);
|
|
273
460
|
};
|
|
274
461
|
|
|
275
462
|
injectStyles();
|
|
@@ -278,10 +465,19 @@ export function setupMobileDrawer() {
|
|
|
278
465
|
document.addEventListener('keydown', onKeyDown, true);
|
|
279
466
|
document.addEventListener('visibilitychange', onVisibilityChange);
|
|
280
467
|
window.addEventListener('resize', onResize);
|
|
468
|
+
window.addEventListener('touchstart', onTouchStart, { passive: true });
|
|
469
|
+
window.addEventListener('touchmove', onTouchMove, { passive: true });
|
|
470
|
+
window.addEventListener('touchend', onTouchEnd, { passive: true });
|
|
471
|
+
window.addEventListener('touchcancel', onTouchCancel, { passive: true });
|
|
472
|
+
|
|
473
|
+
// 等侧栏先无动画地藏到屏幕外,再启用过渡(否则首次加载会看到 280ms 滑出)
|
|
474
|
+
window.requestAnimationFrame(() => {
|
|
475
|
+
if (!disposed && document.body) document.body.classList.add('qzsy-drawer-ready');
|
|
476
|
+
});
|
|
281
477
|
|
|
282
478
|
if (typeof MutationObserver === 'function' && document.body) {
|
|
283
479
|
observer = new MutationObserver(() => {
|
|
284
|
-
if (!document.querySelector(`.${MENU_CLASS}`) || !document.querySelector(`.${BACKDROP_CLASS}`) || !document.getElementById(MOBILE_DRAWER_CSS_ID)) {
|
|
480
|
+
if (!document.querySelector(`.${HEADER_CLASS}`) || !document.querySelector(`.${MENU_CLASS}`) || !document.querySelector(`.${BACKDROP_CLASS}`) || !document.getElementById(MOBILE_DRAWER_CSS_ID)) {
|
|
285
481
|
ensureElements();
|
|
286
482
|
}
|
|
287
483
|
});
|
|
@@ -292,13 +488,20 @@ export function setupMobileDrawer() {
|
|
|
292
488
|
|
|
293
489
|
return () => {
|
|
294
490
|
disposed = true;
|
|
491
|
+
gesture = null;
|
|
295
492
|
try { observer?.disconnect(); } catch { /* 忽略 */ }
|
|
296
493
|
document.removeEventListener('click', onClick, true);
|
|
297
494
|
document.removeEventListener('keydown', onKeyDown, true);
|
|
298
495
|
document.removeEventListener('visibilitychange', onVisibilityChange);
|
|
299
496
|
window.removeEventListener('resize', onResize);
|
|
497
|
+
window.removeEventListener('touchstart', onTouchStart);
|
|
498
|
+
window.removeEventListener('touchmove', onTouchMove);
|
|
499
|
+
window.removeEventListener('touchend', onTouchEnd);
|
|
500
|
+
window.removeEventListener('touchcancel', onTouchCancel);
|
|
501
|
+
clearDrag();
|
|
300
502
|
document.body?.classList.remove(OPEN_CLASS);
|
|
301
|
-
document.
|
|
503
|
+
document.body?.classList.remove('qzsy-drawer-ready');
|
|
504
|
+
document.querySelector(`.${HEADER_CLASS}`)?.remove();
|
|
302
505
|
document.querySelector(`.${BACKDROP_CLASS}`)?.remove();
|
|
303
506
|
document.getElementById(MOBILE_DRAWER_CSS_ID)?.remove();
|
|
304
507
|
root.removeAttribute(MOBILE_UI_FLAG);
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qzsy/dsh-bridge",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.1",
|
|
4
4
|
"description": "DSH 远程隧道云官方客户端插件:一条命令把本机 DeepSeek Harness 接入云端隧道(设备码绑定、短期令牌、本地门禁代理、HTTP/SSE/WebSocket 反向隧道)。",
|
|
5
|
-
"releaseNotes": "【v3.3.
|
|
5
|
+
"releaseNotes": "【v3.3.1】移动端安全区与手势修复\\n• 全面屏:菜单按钮/内容/抽屉按 safe-area-inset 让位,刘海、状态栏、home indicator 不再遮挡\\n• 手势:左缘右滑打开、抽屉内左滑关闭;拖动跟手、遮罩渐变,松手按距离/速度吸附\\n• 纵向滚动不误触抽屉;滑动后的合成点击会被吞掉\\n• 首次加载不再播放侧栏滑出动画",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "lib/index.js",
|
|
8
8
|
"exports": {
|