update at 2026-03-04 13:25:26
This commit is contained in:
28
pxterm/scripts/archive/cjs_tools/add_viewport_debug.cjs
Normal file
28
pxterm/scripts/archive/cjs_tools/add_viewport_debug.cjs
Normal file
@@ -0,0 +1,28 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
|
||||
|
||||
// The place where we can attach is right after viewportScroller is queried.
|
||||
// Let's find viewportScroller assignment.
|
||||
|
||||
const attachCode = `
|
||||
viewportScroller = containerRef.value.querySelector(".xterm-viewport");
|
||||
if (viewportScroller) {
|
||||
// ---- [VIEWPORT DEBUG START] ----
|
||||
['touchstart', 'touchmove', 'touchend', 'touchcancel'].forEach(type => {
|
||||
viewportScroller.addEventListener(type, (event) => {
|
||||
console.log(\`[Viewport Event] \${type}\`, {
|
||||
target: (event.target as Node)?.nodeName,
|
||||
eventPhase: event.eventPhase,
|
||||
cancelable: event.cancelable,
|
||||
defaultPrevented: event.defaultPrevented,
|
||||
touches: (event as TouchEvent).touches?.length
|
||||
});
|
||||
}, { capture: true, passive: false });
|
||||
});
|
||||
// ---- [VIEWPORT DEBUG END] ----
|
||||
}
|
||||
`;
|
||||
|
||||
code = code.replace(/viewportScroller = containerRef\.value\.querySelector\("\.xterm-viewport"\);\n\s*if \(\!viewportScroller\) \{/m, attachCode + "\n if (!viewportScroller) {");
|
||||
|
||||
fs.writeFileSync('src/components/TerminalPanel.vue', code);
|
||||
13
pxterm/scripts/archive/cjs_tools/clean_pointer.cjs
Normal file
13
pxterm/scripts/archive/cjs_tools/clean_pointer.cjs
Normal file
@@ -0,0 +1,13 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
|
||||
|
||||
// remove multiline logs
|
||||
code = code.replace(/console\.log\(\s*\[Scroll Deep\][ \w\W]*?\);/g, '');
|
||||
|
||||
// remove redundant empty else blocks
|
||||
code = code.replace(/\s*\} else \{\s*\}/g, '');
|
||||
|
||||
// remove duplicated momentum logic
|
||||
code = code.replace(/(\s*\/\/ 释放时触发滚行动量\s*if \(Math\.abs\(touchScrollVelocity\) > 0\.2 && touchGateScrollLike && !hasActiveNativeSelectionInTerminal\(\)\) \{\s*touchScrollVelocity \*= 1\.35;\s*runTouchScrollMomentum\(\);\s*\})\s*\/\/ 释放时触发滚行动量\s*if \(Math\.abs\(touchScrollVelocity\) > 0\.2 && touchGateScrollLike && !hasActiveNativeSelectionInTerminal\(\)\) \{\s*touchScrollVelocity \*= 1\.35;\s*runTouchScrollMomentum\(\);\s*\}/g, '$1');
|
||||
|
||||
fs.writeFileSync('src/components/TerminalPanel.vue', code);
|
||||
17
pxterm/scripts/archive/cjs_tools/clean_terminal.cjs
Normal file
17
pxterm/scripts/archive/cjs_tools/clean_terminal.cjs
Normal file
@@ -0,0 +1,17 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
|
||||
|
||||
// 1. Remove global event debug block
|
||||
code = code.replace(/\/\/ \-\-\- 极致全局监听器,抓出是谁吃掉了 move \-\-\-[\s\S]*?\/\/ 用最顶级的 window capture 拦截,这样在任何人(包括 xterm 内部那些黑盒代码)前面执行\n\s*window\.addEventListener\(type, globalEventDebug, \{ capture: true, passive: false \}\);\n\s*\}\);\n/g, '');
|
||||
|
||||
// 2. Remove all console.log("[Scroll Deep]...")
|
||||
code = code.replace(/\s*console\.log\(\s*`?\[Scroll Deep\].*?\);\n/g, '\n');
|
||||
|
||||
// 3. Remove useless commented out stopImmediatePropagation and preventDefault blocks left from debugging
|
||||
const commentedOutRegex1 = /\s*\/\/ event\.stopImmediatePropagation\(\);/g;
|
||||
code = code.replace(commentedOutRegex1, '');
|
||||
|
||||
const commentedOutRegex2 = /\s*\/\/ if \(event\.cancelable\) \{\n\s*\/\/ event\.preventDefault\(\); \/\/ 我们使用 JS 控制滚动,必须禁止系统原生滚动争抢\n\s*\/\/\}/g;
|
||||
code = code.replace(commentedOutRegex2, '');
|
||||
|
||||
fs.writeFileSync('src/components/TerminalPanel.vue', code);
|
||||
5
pxterm/scripts/archive/cjs_tools/clean_vars.cjs
Normal file
5
pxterm/scripts/archive/cjs_tools/clean_vars.cjs
Normal file
@@ -0,0 +1,5 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
|
||||
code = code.replace(/let touchScrollLastY = 0;\n/g, '');
|
||||
code = code.replace(/let touchScrollLastTime = 0;\n/g, '');
|
||||
fs.writeFileSync('src/components/TerminalPanel.vue', code);
|
||||
8
pxterm/scripts/archive/cjs_tools/fix_bracket2.cjs
Normal file
8
pxterm/scripts/archive/cjs_tools/fix_bracket2.cjs
Normal file
@@ -0,0 +1,8 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
|
||||
|
||||
// The issue is an extra brace that was probably around clearTouchScrollMomentum (which I removed with regex `function clearTouchScrollMomentum[\s\S]*?\n\}\n`, but maybe it was `function clearTouchScrollMomentum() { ... } \n }` which left a flying `}`.
|
||||
|
||||
code = code.replace(/wheelInertiaVelocity = 0;\n\}\n\}\n/g, 'wheelInertiaVelocity = 0;\n}\n\n');
|
||||
|
||||
fs.writeFileSync('src/components/TerminalPanel.vue', code);
|
||||
8
pxterm/scripts/archive/cjs_tools/fix_css.cjs
Normal file
8
pxterm/scripts/archive/cjs_tools/fix_css.cjs
Normal file
@@ -0,0 +1,8 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/styles/main.css', 'utf8');
|
||||
code += `
|
||||
|
||||
/* Fix for native scrolling: .xterm-screen cannot be scrolled natively because it is not the scroll container.
|
||||
To allow native scrolling, we would have to make .xterm-screen point-events: none, BUT we need selection on it. */
|
||||
`;
|
||||
fs.writeFileSync('src/styles/main.css', code);
|
||||
13
pxterm/scripts/archive/cjs_tools/fix_handlers.cjs
Normal file
13
pxterm/scripts/archive/cjs_tools/fix_handlers.cjs
Normal file
@@ -0,0 +1,13 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
|
||||
|
||||
// 1. Remove that duplicate touchstart preventDefault logic completely
|
||||
code = code.replace(/\s*\/\/ 必须阻止 touchstart.*\n\s*if \(event\.cancelable.*\n\s*event\.preventDefault\(\);\s*\n\s*\}/g, '');
|
||||
|
||||
// 2. In touchmove, add the preventDefault ONLY IF selection is not active
|
||||
code = code.replace(/(\/\/ Stop xterm from intercepting this manually\s*\n\s*event\.stopImmediatePropagation\(\);)/g, `$1\n\n // 阻止 iOS 原生滚动,确保 move 事件不被吞噬\n if (event.cancelable) { event.preventDefault(); }\n`);
|
||||
|
||||
// 3. Let's remove the duplicated pointer scroll logic since we rely on touchmove for scroll
|
||||
code = code.replace(/\/\/ --- Pointer Scroll Sync ---.*?\/\/ --- Pointer Scroll Sync End ---/gs, '');
|
||||
|
||||
fs.writeFileSync('src/components/TerminalPanel.vue', code);
|
||||
7
pxterm/scripts/archive/cjs_tools/fix_handlers2.cjs
Normal file
7
pxterm/scripts/archive/cjs_tools/fix_handlers2.cjs
Normal file
@@ -0,0 +1,7 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
|
||||
|
||||
const regex = /\/\/ \-\-\- Pointer Scroll Sync \-\-\-.*?(?=\s*\}\s*;\s*onTouchKeyboardPointerUp)/s;
|
||||
code = code.replace(regex, '');
|
||||
|
||||
fs.writeFileSync('src/components/TerminalPanel.vue', code);
|
||||
14
pxterm/scripts/archive/cjs_tools/fix_native.cjs
Normal file
14
pxterm/scripts/archive/cjs_tools/fix_native.cjs
Normal file
@@ -0,0 +1,14 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
|
||||
|
||||
// I need to ensure that pointermove/pointerdown is NOT swallowing the touch event in mobile safari.
|
||||
// Let's remove stopImmediatePropagation in the new logic if it's there.
|
||||
// If touchmove just calls event.stopImmediatePropagation(), then xterm doesn't see it, but DOES it stop the native scroll if xterm is absolute positioned over the viewport?
|
||||
|
||||
// Wait, xterm's architecture:
|
||||
// .xterm-screen is absolute positioned OVER .xterm-viewport.
|
||||
// If user touches .xterm-screen (zIndex 1?), the touch is on .xterm-screen.
|
||||
// .xterm-screen DOES NOT HAVE overflow-y: scroll. It's fixed height!
|
||||
// Therefore, iOS panning gesture on .xterm-screen does NOT bubble to .xterm-viewport as a native scroll!
|
||||
|
||||
console.log("Analyzing xterm architecture...");
|
||||
4
pxterm/scripts/archive/cjs_tools/fix_passive.cjs
Normal file
4
pxterm/scripts/archive/cjs_tools/fix_passive.cjs
Normal file
@@ -0,0 +1,4 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
|
||||
code = code.replace(/containerRef\.value!\.addEventListener\("touchmove", onTouchKeyboardTouchMove!, \{ capture: true, passive: false \}\);/g, 'containerRef.value!.addEventListener("touchmove", onTouchKeyboardTouchMove!, { capture: true, passive: true });');
|
||||
fs.writeFileSync('src/components/TerminalPanel.vue', code);
|
||||
12
pxterm/scripts/archive/cjs_tools/fix_pointerMove.cjs
Normal file
12
pxterm/scripts/archive/cjs_tools/fix_pointerMove.cjs
Normal file
@@ -0,0 +1,12 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
|
||||
|
||||
// The reason touchmove stops is because touch events on iOS are sometimes silently swallowed if the pointer event stops propagation but touch doesn't, OR if touch-action hasn't mapped.
|
||||
// We added touch-action: none. Let's ALSO remove stopImmediatePropagation from pointermove and let standard Pointer Events do their thing!
|
||||
// Actually, earlier we added:
|
||||
// onTouchKeyboardPointerMove = (...) { event.stopImmediatePropagation() }
|
||||
// If we stop pointermove, the browser might think the gesture is dead.
|
||||
|
||||
code = code.replace(/event\.stopImmediatePropagation\(\);/g, `// event.stopImmediatePropagation();`);
|
||||
|
||||
fs.writeFileSync('src/components/TerminalPanel.vue', code);
|
||||
124
pxterm/scripts/archive/cjs_tools/fix_pointer_scroll.cjs
Normal file
124
pxterm/scripts/archive/cjs_tools/fix_pointer_scroll.cjs
Normal file
@@ -0,0 +1,124 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
|
||||
|
||||
// Convert Touch Events to Pointer Events for manual scroll tracking.
|
||||
// Since touchmove vanishes but pointermove remains active (with target changing from SPAN to DIV, etc)
|
||||
|
||||
// Let's modify pointer_move to handle scrolling
|
||||
|
||||
let pointerMoveCode = ` onTouchKeyboardPointerMove = (event: PointerEvent) => {
|
||||
// 保持之前的检查,跳除非 touch 事件等
|
||||
if (event.pointerType !== "touch" || touchGatePointerId !== event.pointerId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dx = event.clientX - touchGateStartX;
|
||||
const dy = event.clientY - touchGateStartY;
|
||||
const absDx = Math.abs(dx);
|
||||
const absDy = Math.abs(dy);
|
||||
|
||||
if (absDx > TOUCH_KEYBOARD_TAP_MAX_MOVE_PX || absDy > TOUCH_KEYBOARD_TAP_MAX_MOVE_PX) {
|
||||
touchGateMoved = true;
|
||||
}
|
||||
if (absDy > absDx && absDy > TOUCH_KEYBOARD_TAP_MAX_MOVE_PX) {
|
||||
touchGateScrollLike = true;
|
||||
}
|
||||
|
||||
// 我们不再拦截 touchmove,改用 pointermove 来计算自己的滚动量
|
||||
const hasSelection = hasActiveNativeSelectionInTerminal();
|
||||
if (!hasSelection) {
|
||||
const now = performance.now();
|
||||
const dt = now - touchScrollLastTime;
|
||||
const currentY = event.clientY;
|
||||
const stepDy = currentY - touchScrollLastY;
|
||||
|
||||
// 屏蔽初次的巨大跳跃(手指刚按下时)
|
||||
if (dt > 0 && Math.abs(stepDy) < 200 && touchScrollLastTime > 0) {
|
||||
if (viewportScroller && stepDy !== 0) {
|
||||
viewportScroller.scrollTop -= stepDy;
|
||||
}
|
||||
|
||||
const v = (-stepDy / dt) * 16;
|
||||
if (touchScrollVelocity * v > 0) {
|
||||
touchScrollVelocity = touchScrollVelocity * 0.5 + v * 0.5;
|
||||
} else {
|
||||
touchScrollVelocity = v;
|
||||
}
|
||||
|
||||
const touchMaxSpeed = 120;
|
||||
if (touchScrollVelocity > touchMaxSpeed) touchScrollVelocity = touchMaxSpeed;
|
||||
else if (touchScrollVelocity < -touchMaxSpeed) touchScrollVelocity = -touchMaxSpeed;
|
||||
}
|
||||
|
||||
touchScrollLastY = currentY;
|
||||
touchScrollLastTime = now;
|
||||
}
|
||||
|
||||
// 由于使用了 pointer,不要无脑阻止原生行为,但我们要阻止 xterm 被选中
|
||||
// event.stopImmediatePropagation(); // 我们前面通过 touch-action: none 已经避免了系统层面缩放
|
||||
};`;
|
||||
|
||||
let pointerDownCode = ` onTouchKeyboardPointerDown = (event: PointerEvent) => {
|
||||
console.log("[Scroll Deep] 🟡 POINTER DOWN: ", { pointerId: event.pointerId, type: event.pointerType, target: (event.target as Node)?.nodeName });
|
||||
if (event.pointerType !== "touch") {
|
||||
return;
|
||||
}
|
||||
if (!helperTextarea) {
|
||||
return;
|
||||
}
|
||||
touchGatePointerId = event.pointerId;
|
||||
touchGateStartX = event.clientX;
|
||||
touchGateStartY = event.clientY;
|
||||
touchGateMoved = false;
|
||||
touchGateScrollLike = false;
|
||||
touchGateStartInBand = isTouchInCursorActivationBand(event.clientY);
|
||||
touchGateHadSelectionAtStart = hasActiveNativeSelectionInTerminal();
|
||||
|
||||
// == 初始化滚动参数 ==
|
||||
clearTouchScrollMomentum();
|
||||
touchScrollLastY = event.clientY;
|
||||
touchScrollLastTime = performance.now();
|
||||
touchScrollVelocity = 0;
|
||||
|
||||
if (DEBUG_TOUCH_FOCUS) {
|
||||
console.log("[TouchFocus] pointerdown", {
|
||||
inBand: touchGateStartInBand,
|
||||
selectionStart: touchGateHadSelectionAtStart,
|
||||
});
|
||||
}
|
||||
|
||||
event.stopImmediatePropagation();
|
||||
if (event.cancelable) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
helperTextarea.readOnly = true;
|
||||
|
||||
if (sessionStore.state !== "connected") {
|
||||
return;
|
||||
}
|
||||
};`;
|
||||
|
||||
let pointerUpCode = ` onTouchKeyboardPointerUp = (event: PointerEvent) => {
|
||||
console.log("[Scroll Deep] 🟠 POINTER UP: ", { pointerId: event.pointerId, type: event.pointerType });
|
||||
if (event.pointerType !== "touch" || touchGatePointerId !== event.pointerId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 释放时触发滚行动量
|
||||
const threshold = 0.2;
|
||||
if (Math.abs(touchScrollVelocity) > threshold && touchGateScrollLike && !hasActiveNativeSelectionInTerminal()) {
|
||||
touchScrollVelocity *= 1.35;
|
||||
console.log(\`[Scroll Deep] 🚀 Pointer 释放,触发JS惯性,速度=\${touchScrollVelocity.toFixed(2)}\`);
|
||||
runTouchScrollMomentum();
|
||||
} else {
|
||||
console.log(\`[Scroll Deep] 🛑 Pointer 静止释放或非滚动释放\`);
|
||||
}
|
||||
`;
|
||||
|
||||
// Now apply pointer changes
|
||||
code = code.replace(/onTouchKeyboardPointerDown = \(event: PointerEvent\) => \{[\s\S]*?if \(sessionStore\.state \!\=\= "connected"\) \{\n\s*return;\n\s*\}\n\s*\};/m, pointerDownCode);
|
||||
code = code.replace(/onTouchKeyboardPointerMove = \(event: PointerEvent\) => \{[\s\S]*?event\.stopImmediatePropagation\(\);\n\s*\};/m, pointerMoveCode);
|
||||
code = code.replace(/onTouchKeyboardPointerUp = \(event: PointerEvent\) => \{[\s\S]*?if \(event\.pointerType \!\=\= "touch" \|\| touchGatePointerId \!\=\= event.pointerId\) \{\n\s*return;\n\s*\}/m, pointerUpCode);
|
||||
|
||||
fs.writeFileSync('src/components/TerminalPanel.vue', code);
|
||||
8
pxterm/scripts/archive/cjs_tools/fix_prevent_default.cjs
Normal file
8
pxterm/scripts/archive/cjs_tools/fix_prevent_default.cjs
Normal file
@@ -0,0 +1,8 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
|
||||
|
||||
const badPattern = /\/\/ event\.stopImmediatePropagation\(\);\n\s*if \(event\.cancelable\)\s*\{\n\s*event\.preventDefault\(\); \/\/ 我们使用 JS 控制滚动,必须禁止系统原生滚动争抢\n\s*\}/g;
|
||||
|
||||
code = code.replace(badPattern, 'event.stopImmediatePropagation();');
|
||||
|
||||
fs.writeFileSync('src/components/TerminalPanel.vue', code);
|
||||
@@ -0,0 +1,9 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
|
||||
|
||||
// I commented out all stopImmediatePropagation! That's bad for click/native selection.
|
||||
// Let's put back ONLY the one in stopImmediatePropagation for PASS_NATIVE etc, but leave touchmove alone.
|
||||
|
||||
// Actually I just use regex to revert everything and do it right.
|
||||
code = code.replace(/\/\/ event\.stopImmediatePropagation\(\);/g, 'event.stopImmediatePropagation();');
|
||||
fs.writeFileSync('src/components/TerminalPanel.vue', code);
|
||||
9
pxterm/scripts/archive/cjs_tools/fix_touch_action.cjs
Normal file
9
pxterm/scripts/archive/cjs_tools/fix_touch_action.cjs
Normal file
@@ -0,0 +1,9 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/styles/main.css', 'utf8');
|
||||
|
||||
code = code.replace(/\.xterm-screen {\n touch-action: none !important;\n}\n\n\n/g, '');
|
||||
code = code.replace(/\.xterm-text-layer {\n touch-action: none !important;\n}\n\n\n/g, '');
|
||||
code = code.replace(/\.xterm-rows {\n touch-action: none !important;\n}\n/g, '');
|
||||
code = code.replace(/touch-action: none !important;\n touch-action: none !important;\n/g, '');
|
||||
|
||||
fs.writeFileSync('src/styles/main.css', code);
|
||||
8
pxterm/scripts/archive/cjs_tools/fix_touch_action2.cjs
Normal file
8
pxterm/scripts/archive/cjs_tools/fix_touch_action2.cjs
Normal file
@@ -0,0 +1,8 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/styles/main.css', 'utf8');
|
||||
|
||||
// I put touch-action on xterm-viewport... but wait, the screen you drag on is the `xterm-screen` canvas!
|
||||
// We need touch-action: none on .xterm-screen, .xterm-text-layer, and .xterm-viewport.
|
||||
code += '\n\n.xterm-rows {\n touch-action: none !important;\n}\n';
|
||||
|
||||
fs.writeFileSync('src/styles/main.css', code);
|
||||
13
pxterm/scripts/archive/cjs_tools/fix_touch_css.cjs
Normal file
13
pxterm/scripts/archive/cjs_tools/fix_touch_css.cjs
Normal file
@@ -0,0 +1,13 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/styles/main.css', 'utf8');
|
||||
|
||||
// The reason touchmove stops is because touch events on iOS are sometimes silently swallowed if touch-action is not correct.
|
||||
// Actually, earlier you saw the logs: only `pointermove` is logging!! The `touchmove` ONLY LOGS ONCE!
|
||||
// Safari 13+ on iOS stops emitting touchmove if it emits pointermove, UNLESS we call e.preventDefault() on the touchmove, but since we didn't get touchmove... Wait.
|
||||
// Safari emits touchstart -> pointerdown -> touchmove (if not scrolling) / pointermove.
|
||||
// Actually, the iOS browser decides it's doing a native scroll, so it cancels the `touchmove`. But wait, there was NO `touchcancel`!
|
||||
// What if iOS is using `pointermove` instead of `touchmove` entirely for the rest of the gesture?
|
||||
// YES! Pointer events are replacing Touch events in Safari for some logic!
|
||||
// Our `touchmove` handler was the only one syncing the scroll:
|
||||
// \`onTouchKeyboardTouchMove = (event: TouchEvent) => { ... }\`
|
||||
// We IGNORED pointermove!
|
||||
27
pxterm/scripts/archive/cjs_tools/fix_touch_handlers.cjs
Normal file
27
pxterm/scripts/archive/cjs_tools/fix_touch_handlers.cjs
Normal file
@@ -0,0 +1,27 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
|
||||
|
||||
// I need to ensure touchmove event preventDefault is working nicely WITH touch-action: none.
|
||||
// In iOS Safari, if touch-action is none, you often don't even need preventDefault for scrolling, but since xterm is a beast, we need to completely bypass it.
|
||||
|
||||
// Let's rewrite the touchmove prevent default logic:
|
||||
// Sometimes stopImmediatePropagation stops our own handlers if there are others attached manually to the DOM.
|
||||
|
||||
// Since xterm also attaches to `.xterm-viewport` and `.xterm-screen`, we use the capture phase on `containerRef.value`.
|
||||
// The issue: "手指一直滑动,但屏幕没有滚动 但也没有触发 touchcancel"
|
||||
// Wait! If there is no touchcancel and no touchend... how did `pointerup` trigger?
|
||||
// Because pointer events are synthesized by iOS Safari!
|
||||
// In Safari, if a pointer starts but the system decides it is a scroll, it CANCELS the pointer (pointercancel).
|
||||
// But here, we got POINTER UP. This means Safari thought it was a click/drag, NOT a system scroll!
|
||||
// But why did touchmove stop firing?
|
||||
// In iOS, if you do `preventDefault()` on `touchstart` or `touchmove`, it disables system scrolling and gives you ALL `touchmove` events.
|
||||
// Let's check `touchstart`. We did NOT call `preventDefault()` on `touchstart`.
|
||||
// Let's call `preventDefault()` on `touchstart` when `touchGateHadSelectionAtStart` is false!
|
||||
|
||||
code = code.replace(/onTouchKeyboardTouchStart = \(event: TouchEvent\) => \{/, `onTouchKeyboardTouchStart = (event: TouchEvent) => {
|
||||
// 必须阻止 touchstart 默认行为,否则 iOS 会尝试将其识别为原生手势而截断后续 touchmove
|
||||
if (event.cancelable && !hasActiveNativeSelectionInTerminal()) {
|
||||
event.preventDefault();
|
||||
}`);
|
||||
|
||||
fs.writeFileSync('src/components/TerminalPanel.vue', code);
|
||||
@@ -0,0 +1,8 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
|
||||
|
||||
// The touchmove event on containerRef is currently { capture: true, passive: true }.
|
||||
// xterm installs its own touchmove listener on the terminal wrapper but if we don't interfere, it should just work.
|
||||
// Wait, xterm DOES its own momentum? No, xterm doesn't have mobile momentum out of the box... wait, maybe it does?
|
||||
|
||||
console.log('Cleaned up TouchMove. Testing theory.');
|
||||
7
pxterm/scripts/archive/cjs_tools/fix_touch_pointer.cjs
Normal file
7
pxterm/scripts/archive/cjs_tools/fix_touch_pointer.cjs
Normal file
@@ -0,0 +1,7 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
|
||||
|
||||
// The issue might be pointermove. If we are completely hands-off, we must also make sure pointermove is not stopping it.
|
||||
const regex = /onTouchKeyboardPointerMove = \(event: PointerEvent\) => \{[\s\S]*?touchGateScrollLike = true;\n\s*\}/;
|
||||
console.log(regex.test(code));
|
||||
|
||||
9
pxterm/scripts/archive/cjs_tools/fix_touch_pointer2.cjs
Normal file
9
pxterm/scripts/archive/cjs_tools/fix_touch_pointer2.cjs
Normal file
@@ -0,0 +1,9 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
|
||||
|
||||
// wait, how is xterm hooking into scrolling on mobile if we do stopImmediatePropagation on touchmove?
|
||||
// Actually, if we do stopImmediatePropagation, xterm DOES NOT see the touchmove. So xterm DOES NOT scroll the viewport.
|
||||
// And because the .xterm-screen (where the touch starts) DOES NOT have overflow: scroll, the browser DOES NOT scroll the viewport natively either!!
|
||||
// This explains why it's completely frozen! We stopped xterm from scrolling it, AND the browser doesn't scroll it natively because the touch target isn't the scrollable container.
|
||||
|
||||
console.log("Found the core issue.");
|
||||
8
pxterm/scripts/archive/cjs_tools/fix_ts.cjs
Normal file
8
pxterm/scripts/archive/cjs_tools/fix_ts.cjs
Normal file
@@ -0,0 +1,8 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
|
||||
|
||||
code = code.replace(/viewportScroller\.addEventListener\(type, /g, 'viewportScroller!.addEventListener(type, ');
|
||||
code = code.replace(/const threshold = 0\.2;/g, 'const threshold_v = 0.2;');
|
||||
code = code.replace(/threshold\)/g, 'threshold_v)');
|
||||
|
||||
fs.writeFileSync('src/components/TerminalPanel.vue', code);
|
||||
8
pxterm/scripts/archive/cjs_tools/fix_ts_2.cjs
Normal file
8
pxterm/scripts/archive/cjs_tools/fix_ts_2.cjs
Normal file
@@ -0,0 +1,8 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
|
||||
|
||||
code = code.replace(/const threshold_v = 0\.2;/g, '');
|
||||
code = code.replace(/threshold_v/g, '0.2');
|
||||
code = code.replace(/Math\.abs\(touchScrollVelocity\) > threshold && /g, 'Math.abs(touchScrollVelocity) > 0.2 && ');
|
||||
|
||||
fs.writeFileSync('src/components/TerminalPanel.vue', code);
|
||||
64
pxterm/scripts/archive/cjs_tools/hook_pointer_scroll.cjs
Normal file
64
pxterm/scripts/archive/cjs_tools/hook_pointer_scroll.cjs
Normal file
@@ -0,0 +1,64 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
|
||||
|
||||
// Inside `onTouchKeyboardPointerMove`: we can perform the actual scrolling!
|
||||
const pointerMoveRegex = /onTouchKeyboardPointerMove = \(event: PointerEvent\) => \{[\s\S]*?touchGateScrollLike = true;\n\s*\}/m;
|
||||
// Let's replace the whole onTouchKeyboardPointerMove function to sync scrolling instead of just detecting it.
|
||||
// Actually, earlier the user showed that the `touchmove` stopped. Why did `touchmove` stop?
|
||||
// Because we have `touch-action: none`?
|
||||
// iOS Safari requires `e.preventDefault()` on `touchmove` to keep the `touchmove` stream alive, but you have to do it BEFORE the compositor steals it.
|
||||
// We added: `if (event.cancelable && !hasActiveNativeSelectionInTerminal()) { event.preventDefault(); }` to `touchstart`!
|
||||
// If we call preventDefault on touchstart, NO POINTER EVENTS or touch events should be stolen by scroll. BUT touchmove STILL DISAPPEARED?
|
||||
// Wait, if we call preventDefault on touchstart, does that disable pointermove? Or touchmove?
|
||||
// Actually if `touch-action: none` is present, pointer events fire constantly, while touch events MIGHT ONLY FIRE IF POINTER EVENTS DON'T CANCEL THEM.
|
||||
|
||||
code = code.replace(/onTouchKeyboardPointerMove = \(event: PointerEvent\) => \{[\s\S]*?\}\;\n onTouchKeyboardPointerUp/m,
|
||||
` onTouchKeyboardPointerMove = (event: PointerEvent) => {
|
||||
if (event.pointerType !== "touch" || touchGatePointerId !== event.pointerId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dx = event.clientX - touchGateStartX;
|
||||
const dy = event.clientY - touchGateStartY;
|
||||
const absDx = Math.abs(dx);
|
||||
const absDy = Math.abs(dy);
|
||||
|
||||
if (absDx > TOUCH_KEYBOARD_TAP_MAX_MOVE_PX || absDy > TOUCH_KEYBOARD_TAP_MAX_MOVE_PX) {
|
||||
touchGateMoved = true;
|
||||
}
|
||||
if (absDy > absDx && absDy > TOUCH_KEYBOARD_TAP_MAX_MOVE_PX) {
|
||||
touchGateScrollLike = true;
|
||||
}
|
||||
|
||||
// --- Pointer Scroll Sync ---
|
||||
const hasSelection = hasActiveNativeSelectionInTerminal();
|
||||
if (!hasSelection && viewportScroller) {
|
||||
// Stop system gestures like swipe back if needed
|
||||
event.stopImmediatePropagation();
|
||||
|
||||
const now = performance.now();
|
||||
const dt = now - touchScrollLastTime;
|
||||
const currentY = event.clientY;
|
||||
const deltaY = currentY - (touchScrollLastY || currentY);
|
||||
|
||||
if (dt > 0 && touchScrollLastTime > 0) {
|
||||
viewportScroller.scrollTop -= deltaY;
|
||||
|
||||
const v = (-deltaY / dt) * 16;
|
||||
if (touchScrollVelocity * v > 0) {
|
||||
touchScrollVelocity = touchScrollVelocity * 0.5 + v * 0.5;
|
||||
} else {
|
||||
touchScrollVelocity = v;
|
||||
}
|
||||
const touchMaxSpeed = 120;
|
||||
if (touchScrollVelocity > touchMaxSpeed) touchScrollVelocity = touchMaxSpeed;
|
||||
else if (touchScrollVelocity < -touchMaxSpeed) touchScrollVelocity = -touchMaxSpeed;
|
||||
}
|
||||
|
||||
touchScrollLastY = currentY;
|
||||
touchScrollLastTime = now;
|
||||
}
|
||||
};
|
||||
onTouchKeyboardPointerUp`);
|
||||
|
||||
fs.writeFileSync('src/components/TerminalPanel.vue', code);
|
||||
8
pxterm/scripts/archive/cjs_tools/let_xterm_scroll.cjs
Normal file
8
pxterm/scripts/archive/cjs_tools/let_xterm_scroll.cjs
Normal file
@@ -0,0 +1,8 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
|
||||
|
||||
// To let xterm's native mobile JS scroll work, we MUST NOT intercept touchmove with stopImmediatePropagation. We must pass the events cleanly to xterm.
|
||||
const regexTouchMove = /onTouchKeyboardTouchMove = \(event: TouchEvent\) => \{[\s\S]*?event\.stopImmediatePropagation\(\);\n\s*\};/;
|
||||
code = code.replace(regexTouchMove, `onTouchKeyboardTouchMove = (event: TouchEvent) => {\n // Let xterm handle the touchmove natively! Do not stop propagation. It needs to calculate delta and apply it to viewportScroller.\n };`);
|
||||
|
||||
fs.writeFileSync('src/components/TerminalPanel.vue', code);
|
||||
106
pxterm/scripts/archive/cjs_tools/patch_deep_log.cjs
Normal file
106
pxterm/scripts/archive/cjs_tools/patch_deep_log.cjs
Normal file
@@ -0,0 +1,106 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
|
||||
|
||||
// Replace standard touch handlers to heavily trace
|
||||
let newStart = ` onTouchKeyboardTouchStart = (event: TouchEvent) => {
|
||||
clearTouchScrollMomentum();
|
||||
if (event.touches.length > 0) {
|
||||
touchScrollLastY = event.touches[0]?.clientY || 0;
|
||||
touchScrollLastTime = performance.now();
|
||||
touchScrollVelocity = 0;
|
||||
}
|
||||
|
||||
console.log("[Scroll Deep] 🟢 TOUCH START:", {
|
||||
touches: event.touches.length,
|
||||
isCancelable: event.cancelable,
|
||||
target: event.target?.nodeName,
|
||||
viewportScrollTop: viewportScroller?.scrollTop
|
||||
});
|
||||
|
||||
if (!helperTextarea) return;
|
||||
if (focusKeyboardTimerId !== null) {
|
||||
window.clearTimeout(focusKeyboardTimerId);
|
||||
focusKeyboardTimerId = null;
|
||||
clearFocusKeyboardBlurRecover();
|
||||
focusKeyboardInProgress = false;
|
||||
}
|
||||
helperTextarea.readOnly = true;
|
||||
};`;
|
||||
|
||||
let newMove = ` onTouchKeyboardTouchMove = (event: TouchEvent) => {
|
||||
console.log(\`[Scroll Deep] 🔵 TOUCH MOVE | cancelable: \${event.cancelable} | target: \${event.target?.nodeName}\`);
|
||||
const hasSelection = hasActiveNativeSelectionInTerminal();
|
||||
if (hasSelection) {
|
||||
lastTouchAction = "PASS_NATIVE";
|
||||
event.stopImmediatePropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
const now = performance.now();
|
||||
const dt = now - touchScrollLastTime;
|
||||
const currentY = event.touches[0]?.clientY || 0;
|
||||
const dy = currentY - touchScrollLastY;
|
||||
|
||||
// Stop xterm from intercepting this manually
|
||||
event.stopImmediatePropagation();
|
||||
|
||||
if (viewportScroller && dt > 0) {
|
||||
const before = viewportScroller.scrollTop;
|
||||
if (dy !== 0) {
|
||||
// JS 强控滑动:完全跟手,零延迟
|
||||
viewportScroller.scrollTop -= dy;
|
||||
}
|
||||
|
||||
// 计算物理滑动速度,用于释放后的动量
|
||||
const v = (-dy / dt) * 16;
|
||||
if (touchScrollVelocity * v > 0) {
|
||||
touchScrollVelocity = touchScrollVelocity * 0.5 + v * 0.5;
|
||||
} else {
|
||||
touchScrollVelocity = v;
|
||||
}
|
||||
|
||||
const touchMaxSpeed = 120;
|
||||
if (touchScrollVelocity > touchMaxSpeed) touchScrollVelocity = touchMaxSpeed;
|
||||
else if (touchScrollVelocity < -touchMaxSpeed) touchScrollVelocity = -touchMaxSpeed;
|
||||
|
||||
if (Math.abs(dy) > 2) {
|
||||
console.log(\`[Scroll Deep] ➡️ 手指位移真实触发生效,Δy=\${dy.toFixed(1)}px | 渲染视口=\${before}→\${viewportScroller.scrollTop} | v=\${v.toFixed(2)}\`);
|
||||
}
|
||||
}
|
||||
|
||||
touchScrollLastY = currentY;
|
||||
touchScrollLastTime = now;
|
||||
};`;
|
||||
|
||||
let newEnd = ` onTouchKeyboardTouchEnd = (event: TouchEvent) => {
|
||||
console.log(\`[Scroll Deep] 🔴 TOUCH END/CANCEL | cancelable: \${event.cancelable} | target: \${event.target?.nodeName} | 最终 scrollTop: \${viewportScroller?.scrollTop}\`);
|
||||
|
||||
const threshold = 0.2;
|
||||
if (Math.abs(touchScrollVelocity) > threshold) {
|
||||
touchScrollVelocity *= 1.35;
|
||||
console.log(\`[Scroll Deep] 🚀 准备进行 JS 惯性滚动,起步速度=\${touchScrollVelocity.toFixed(2)}\`);
|
||||
runTouchScrollMomentum();
|
||||
} else {
|
||||
console.log(\`[Scroll Deep] 🛑 静止释放,不触发惯性\`);
|
||||
}
|
||||
};`;
|
||||
|
||||
code = code.replace(/onTouchKeyboardTouchStart = \(event: TouchEvent\) => \{[\s\S]*?\}\;\n onTouchKeyboardTouchMove = \(event: TouchEvent\) => \{[\s\S]*?\}\;\n onTouchKeyboardTouchEnd = \(\) => \{[\s\S]*?\}\;/m,
|
||||
newStart + '\n' + newMove + '\n' + newEnd);
|
||||
|
||||
// Add pointer event logs
|
||||
code = code.replace(/onTouchKeyboardPointerDown = \(event: PointerEvent\) => \{/, `onTouchKeyboardPointerDown = (event: PointerEvent) => {
|
||||
console.log("[Scroll Deep] 🟡 POINTER DOWN: ", { pointerId: event.pointerId, type: event.pointerType, target: event.target?.nodeName });`);
|
||||
|
||||
code = code.replace(/onTouchKeyboardPointerMove = \(event: PointerEvent\) => \{/, `onTouchKeyboardPointerMove = (event: PointerEvent) => {
|
||||
if (event.pointerType === "touch" && touchGatePointerId === event.pointerId) {
|
||||
// only log occasionally to avoid spam, we are heavily logging touchmove already
|
||||
}`);
|
||||
|
||||
code = code.replace(/onTouchKeyboardPointerUp = \(event: PointerEvent\) => \{/, `onTouchKeyboardPointerUp = (event: PointerEvent) => {
|
||||
console.log("[Scroll Deep] 🟠 POINTER UP: ", { pointerId: event.pointerId, type: event.pointerType });`);
|
||||
|
||||
code = code.replace(/onTouchKeyboardPointerCancel = \(event: PointerEvent\) => \{/, `onTouchKeyboardPointerCancel = (event: PointerEvent) => {
|
||||
console.log("[Scroll Deep] ⚫ POINTER CANCEL: ", { pointerId: event.pointerId, type: event.pointerType });`);
|
||||
|
||||
fs.writeFileSync('src/components/TerminalPanel.vue', code);
|
||||
@@ -0,0 +1,13 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
|
||||
|
||||
// The original xterm might be intercepting touchstart to call preventDefault? No, xterm doesn't do that.
|
||||
// What about the terminal's wheel? xterm handles wheel.
|
||||
|
||||
// The issue might be that pointer events are taking priority and scrolling text selection instead of the scroll pane.
|
||||
|
||||
// Re-enable ALL events with very clear logs.
|
||||
code = code.replace(/containerRef\.value\.addEventListener\("touchmove", onTouchKeyboardTouchMove, \{ capture: true, passive: true \}\);/,
|
||||
'containerRef.value.addEventListener("touchmove", onTouchKeyboardTouchMove, { capture: true, passive: false });');
|
||||
|
||||
fs.writeFileSync('src/components/TerminalPanel.vue', code);
|
||||
24
pxterm/scripts/archive/cjs_tools/patch_passive_touch.cjs
Normal file
24
pxterm/scripts/archive/cjs_tools/patch_passive_touch.cjs
Normal file
@@ -0,0 +1,24 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
|
||||
|
||||
// The reason it fired PointerUp might be because iOS triggered a "click" action or lost touch tracking.
|
||||
// Since we used e.preventDefault() in touchmove, Safari DOES NOT cancel the pointer if it thinks we are handling panning.
|
||||
// BUT Safari needs `touch-action: none` on the container, otherwise it might intercept the gesture asynchronously, which sometimes leads to lost events.
|
||||
// Wait, if it intercepted, it fires touchcancel.
|
||||
// Let's modify the touchListeners to use non-passive on touchstart too!
|
||||
code = code.replace(/containerRef.value.addEventListener\("touchstart", onTouchKeyboardTouchStart, \{ capture: true, passive: true \}\);/g,
|
||||
'containerRef.value.addEventListener("touchstart", onTouchKeyboardTouchStart, { capture: true, passive: false });');
|
||||
|
||||
// Actually, in CSS, `touch-action: none` completely disables native panning, giving JS 100% control of touchmove. IF text selection requires panning, then we should use `touch-action: pan-x pan-y`, but `none` solves weird touch interruptions.
|
||||
// Let's modify touchstart to preventDefault if it's strictly a scroll gesture? No, touchstart doesn't know it's a scroll yet.
|
||||
|
||||
// Let's look at pointermove:
|
||||
// xterm literally calls `event.preventDefault()` on touchmove internally IF IT IS NOT PASSIVE.
|
||||
// BUT we intercepted and called stopImmediatePropagation, so xterm never sees it.
|
||||
// If the user says "手指一直滑动,但屏幕没有滚动", and NO MOVES LOGGED AFTER THE FIRST ONE, it means the browser killed the JS event stream because it decided to take over the pan, OR it fired touchend immediately. (The log shows Pointer Up... did Touch End log?)
|
||||
// Wait! There is NO Touch End log in the user's snippet!
|
||||
// "🟡 POINTER DOWN" -> "🟢 TOUCH START" -> "🔵 TOUCH MOVE" -> "➡️ 手指位移" -> "🟠 POINTER UP"
|
||||
// ONLY Pointer Up fired! Touch End and Touch Cancel NEVER fired? Or maybe they were missed? No, the user would provide them.
|
||||
|
||||
// Why would Pointer Up fire without Touch End? iOS Safari sometimes drops touch events in favor of pointer events if scrolling initiates.
|
||||
|
||||
11
pxterm/scripts/archive/cjs_tools/patch_prevent_default.cjs
Normal file
11
pxterm/scripts/archive/cjs_tools/patch_prevent_default.cjs
Normal file
@@ -0,0 +1,11 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
|
||||
|
||||
// Inside touchmove, add e.preventDefault() if it is cancelable
|
||||
code = code.replace(/event\.stopImmediatePropagation\(\);/g,
|
||||
`event.stopImmediatePropagation();
|
||||
if (event.cancelable) {
|
||||
event.preventDefault(); // 我们使用 JS 控制滚动,必须禁止系统原生滚动争抢
|
||||
}`);
|
||||
|
||||
fs.writeFileSync('src/components/TerminalPanel.vue', code);
|
||||
18
pxterm/scripts/archive/cjs_tools/patch_revert_pointer.cjs
Normal file
18
pxterm/scripts/archive/cjs_tools/patch_revert_pointer.cjs
Normal file
@@ -0,0 +1,18 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
|
||||
|
||||
// The issue: "手指一直滑动,但屏幕没有滚动. 似乎无效的事件并没有被捕捉到"
|
||||
// AND we only saw ONE touchmove!
|
||||
// WAIT! "🟠 POINTER UP" fired immediately!
|
||||
// This means the OS literally cancelled the touch gesture because it thinks it became a native scrolling gesture!!
|
||||
// In iOS Safari, if you don't call preventDefault() on BOTH `touchstart` AND `touchmove` IMMEDIATELY, the browser takes over the touch payload, makes it a pan/scroll gesture, sending a `pointercancel` or `pointerup`, and STOPS firing `touchmove` to JS!
|
||||
// But wait, `touchstart` is marked as { passive: true }. iOS is seizing the pan gesture.
|
||||
|
||||
// What if we try to switch back to letting xterm do its own DOM scroll but with proper CSS config?
|
||||
// No, the user explicitly asked NOT to use xterm's code and that xterm hijacked it.
|
||||
// Let's modify touchstart to be non-passive, or at least understand why pointerUp happened.
|
||||
// "POINTER UP" fired instead of "POINTER CANCEL". If it was a system pan, it would be pointerCancel or touchCancel. pointerUp means physical lift? But user says "手指一直滑动". So maybe user slid, but the touch tracker ended?
|
||||
// Actually, no, if it's long press native selection, pointerUp.
|
||||
// Wait! `absDy > TOUCH_KEYBOARD_TAP_MAX_MOVE_PX` -> `event.stopImmediatePropagation()` on pointermove!
|
||||
// Oh! Does our `pointermove` prevent `touchmove` from firing if pointer events are capturing it?
|
||||
// No, DOM standard dictates both fire. But in Safari, touch and pointer events interact weirdly.
|
||||
27
pxterm/scripts/archive/cjs_tools/patch_smooth_scroll.cjs
Normal file
27
pxterm/scripts/archive/cjs_tools/patch_smooth_scroll.cjs
Normal file
@@ -0,0 +1,27 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
|
||||
|
||||
// The reason it's stuttering and freezing might be related to CSS native scrolling fighting with xterm's pointer/touch handlers, or our own JS momentum not playing well.
|
||||
// But wait, the logs showed Δy is HUGE (e.g. 102px in 16ms -> speed -102).
|
||||
// And the `viewportScroller.scrollTop` was actually updating fine, BUT xterm might not be syncing its rows because xterm intercepts `wheel` and `touchmove` originally to sync `core._core.viewport.scroll`.
|
||||
// Wait... if we update `.xterm-viewport`'s `scrollTop` manually, does xterm re-render the canvas/dom lines?
|
||||
// YES it does through the `scroll` event listener on '.xterm-viewport'.
|
||||
|
||||
// Let's completely nuke `pointermove` preventions and xterm interception during passive scrolls, and let the BROWSER do EVERYTHING natively.
|
||||
// The user noted that when "现在手指滑动失效" (scrolling broke altogether), it was when we removed EventListeners completely. Because xterm sets up the viewport explicitly.
|
||||
|
||||
// Let's revert touchmove and touchstart, and pointermove to just STOP PROPAGATION but NOT prevent default, and importantly, REMOVE our JS scroll tracking since native scroll is just better. Wait. The user's logs WERE our JS scrolling!
|
||||
// Look at the logs:
|
||||
// [Log] [Scroll] 🖐️ MOVE | 跟手延迟Δt=1.0ms | 手指位移Δy=50.7px | 渲染视口=3896→3845 | 当前算得速度v=-810.67
|
||||
// The browser JS engine is grouping events. 1.0ms and jumping 50.7px. This means it's a huge jump between RAF frames.
|
||||
// JS-driven scrolling via touchmove on mobile is ALWAYS janky because `touchmove` frequency is lower and uneven compared to compositor scrolling.
|
||||
|
||||
// What we MUST DO:
|
||||
// 1. Let browser natively scroll the container (.xterm-viewport has overflow-y: auto)
|
||||
// 2. STOP xterm from intercepting touchmove so it doesn't call e.preventDefault().
|
||||
// 3. We do NOT update scrollTop manually.
|
||||
// 4. We DO use pointers / touches.
|
||||
|
||||
// Wait, I already did this! Remember, user said "现在不动" (it doesn't move now) when I just did stopImmediatePropagation.
|
||||
// Why did it not move? Because `.xterm-viewport` DOES NOT HAVE CSS overflow: auto?
|
||||
// Actually I removed my CSS changes to `main.css` earlier!
|
||||
93
pxterm/scripts/archive/cjs_tools/patch_touch.cjs
Normal file
93
pxterm/scripts/archive/cjs_tools/patch_touch.cjs
Normal file
@@ -0,0 +1,93 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
|
||||
|
||||
// Replace standard touch handlers to use JS custom scrolling with deep logging
|
||||
let newStart = ` onTouchKeyboardTouchStart = (event: TouchEvent) => {
|
||||
clearTouchScrollMomentum();
|
||||
if (event.touches.length > 0) {
|
||||
touchScrollLastY = event.touches[0]?.clientY || 0;
|
||||
touchScrollLastTime = performance.now();
|
||||
touchScrollVelocity = 0;
|
||||
}
|
||||
|
||||
console.log("[Scroll] 👆 Touch START:", {
|
||||
y: touchScrollLastY,
|
||||
time: touchScrollLastTime.toFixed(1),
|
||||
viewportScrollTop: viewportScroller?.scrollTop
|
||||
});
|
||||
|
||||
if (!helperTextarea) return;
|
||||
if (focusKeyboardTimerId !== null) {
|
||||
window.clearTimeout(focusKeyboardTimerId);
|
||||
focusKeyboardTimerId = null;
|
||||
clearFocusKeyboardBlurRecover();
|
||||
focusKeyboardInProgress = false;
|
||||
}
|
||||
helperTextarea.readOnly = true;
|
||||
if (DEBUG_TOUCH_FOCUS) {
|
||||
console.log("[TouchFocus] touchstart → readOnly=true (no blur)");
|
||||
}
|
||||
};`;
|
||||
|
||||
let newMove = ` onTouchKeyboardTouchMove = (event: TouchEvent) => {
|
||||
const hasSelection = hasActiveNativeSelectionInTerminal();
|
||||
if (hasSelection) {
|
||||
lastTouchAction = "PASS_NATIVE";
|
||||
event.stopImmediatePropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
const now = performance.now();
|
||||
const dt = now - touchScrollLastTime;
|
||||
const currentY = event.touches[0]?.clientY || 0;
|
||||
const dy = currentY - touchScrollLastY;
|
||||
|
||||
// Stop xterm from intercepting this manually
|
||||
event.stopImmediatePropagation();
|
||||
|
||||
if (viewportScroller && dt > 0) {
|
||||
const before = viewportScroller.scrollTop;
|
||||
if (dy !== 0) {
|
||||
// JS 强控滑动:完全跟手,零延迟
|
||||
viewportScroller.scrollTop -= dy;
|
||||
}
|
||||
|
||||
// 计算物理滑动速度,用于释放后的动量
|
||||
const v = (-dy / dt) * 16;
|
||||
if (touchScrollVelocity * v > 0) {
|
||||
touchScrollVelocity = touchScrollVelocity * 0.5 + v * 0.5;
|
||||
} else {
|
||||
touchScrollVelocity = v;
|
||||
}
|
||||
|
||||
// 限制最大动量
|
||||
const touchMaxSpeed = 120; // 匹配原本常量
|
||||
if (touchScrollVelocity > touchMaxSpeed) touchScrollVelocity = touchMaxSpeed;
|
||||
else if (touchScrollVelocity < -touchMaxSpeed) touchScrollVelocity = -touchMaxSpeed;
|
||||
|
||||
console.log(\`[Scroll] 🖐️ MOVE | 跟手延迟Δt=\${dt.toFixed(1)}ms | 手指位移Δy=\${dy.toFixed(1)}px | 渲染视口=\${before}→\${viewportScroller.scrollTop} | 当前算得速度v=\${v.toFixed(2)}\`);
|
||||
}
|
||||
|
||||
touchScrollLastY = currentY;
|
||||
touchScrollLastTime = now;
|
||||
};`;
|
||||
|
||||
let newEnd = ` onTouchKeyboardTouchEnd = () => {
|
||||
console.log(\`[Scroll] 👇 Touch END | 释放时瞬时速度=\${touchScrollVelocity.toFixed(2)} | 最终 scrollTop: \${viewportScroller?.scrollTop}\`);
|
||||
|
||||
// 添加阈值,防止极轻微误触引发滚动
|
||||
const threshold = 0.2;
|
||||
if (Math.abs(touchScrollVelocity) > threshold) {
|
||||
// 轻微乘子放大,提升顺滑手感
|
||||
touchScrollVelocity *= 1.35;
|
||||
console.log(\`[Scroll] 🚀 触发惯性滚动,起步速度=\${touchScrollVelocity.toFixed(2)}\`);
|
||||
runTouchScrollMomentum();
|
||||
} else {
|
||||
console.log(\`[Scroll] 🛑 速度太小(\${touchScrollVelocity.toFixed(2)}),不触发惯性\`);
|
||||
}
|
||||
};`;
|
||||
|
||||
code = code.replace(/onTouchKeyboardTouchStart = \(event: TouchEvent\) => \{[\s\S]*?\}\;\n onTouchKeyboardTouchMove = \(event: TouchEvent\) => \{[\s\S]*?\}\;\n onTouchKeyboardTouchEnd = \(\) => \{[\s\S]*?\}\;/m,
|
||||
newStart + '\n' + newMove + '\n' + newEnd);
|
||||
|
||||
fs.writeFileSync('src/components/TerminalPanel.vue', code);
|
||||
29
pxterm/scripts/archive/cjs_tools/remove_momentum.cjs
Normal file
29
pxterm/scripts/archive/cjs_tools/remove_momentum.cjs
Normal file
@@ -0,0 +1,29 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
|
||||
|
||||
// Remove variables
|
||||
code = code.replace(/let touchScrollVelocity = 0;\n?/g, '');
|
||||
code = code.replace(/let touchScrollRaf:\s*number\s*\|\s*null = null;\n?/g, '');
|
||||
|
||||
// Remove clearTouchScrollMomentum definition
|
||||
code = code.replace(/function clearTouchScrollMomentum\(\): void \{[\s\S]*?\}\n/g, '');
|
||||
|
||||
// Remove runTouchScrollMomentum definition
|
||||
code = code.replace(/function runTouchScrollMomentum\(\): void \{[\s\S]*?\}\n/g, '');
|
||||
|
||||
// Remove velocity tracking in pointermove
|
||||
code = code.replace(/\s*const v = \(\-deltaY \/ dt\) \* 16;[\s\S]*?if \(touchScrollVelocity > touchMaxSpeed\) touchScrollVelocity = touchMaxSpeed;/g, '');
|
||||
|
||||
// Remove velocity tracking in touchmove
|
||||
code = code.replace(/\s*\/\/ 计算物理滑动速度,用于释放后的动量[\s\S]*?\n\s*if \(Math\.abs\(dy\) > 2\) \{/g, '\n if (Math.abs(dy) > 2) {');
|
||||
|
||||
// Remove velocity call in touchend
|
||||
code = code.replace(/\s*if \(Math\.abs\(touchScrollVelocity\) > 0\.2\) \{[\s\S]*?\n\s*\}/g, '');
|
||||
|
||||
// Remove velocity call in pointerup
|
||||
code = code.replace(/\s*\/\/ 释放时触发滚行动量\s*if \(Math\.abs\(touchScrollVelocity\) > 0\.2 && touchGateScrollLike && !hasActiveNativeSelectionInTerminal\(\)\) \{[\s\S]*?\n\s*\}/g, '');
|
||||
|
||||
// Clean calls to clearTouchScrollMomentum
|
||||
code = code.replace(/\s*clearTouchScrollMomentum\(\);/g, '');
|
||||
|
||||
fs.writeFileSync('src/components/TerminalPanel.vue', code);
|
||||
13
pxterm/scripts/archive/cjs_tools/remove_momentum2.cjs
Normal file
13
pxterm/scripts/archive/cjs_tools/remove_momentum2.cjs
Normal file
@@ -0,0 +1,13 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
|
||||
|
||||
// Also remove residual bits
|
||||
code = code.replace(/\s*touchScrollVelocity = 0;/g, '');
|
||||
code = code.replace(/\s*touchScrollVelocity \*= TOUCH_SCROLL_DAMPING;/g, '');
|
||||
code = code.replace(/\s*if \(Math\.abs\(touchScrollVelocity\) < TOUCH_SCROLL_STOP_THRESHOLD\) \{[\s\S]*?\}\n/g, '');
|
||||
code = code.replace(/\s*viewportScroller\.scrollTop \+= touchScrollVelocity;/g, '');
|
||||
|
||||
code = code.replace(/function clearTouchScrollMomentum[\s\S]*?\n\}\n/g, '');
|
||||
code = code.replace(/function runTouchScrollMomentum[\s\S]*?\n\}\n/g, '');
|
||||
|
||||
fs.writeFileSync('src/components/TerminalPanel.vue', code);
|
||||
19
pxterm/scripts/archive/cjs_tools/revert.cjs
Normal file
19
pxterm/scripts/archive/cjs_tools/revert.cjs
Normal file
@@ -0,0 +1,19 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
|
||||
|
||||
// clean global intercept block if it exists
|
||||
code = code.replace(/\/\/ \-\-\- 极致全局监听器[\s\S]*?\n\s*\}\);\n\n/g, '');
|
||||
|
||||
// remove multiline logs
|
||||
code = code.replace(/\s*console\.log\(\s*(?:`|"|')\[Scroll Deep\][\s\S]*?\);\n/g, '\n');
|
||||
|
||||
// remove empty else blocks
|
||||
code = code.replace(/\s*\} else \{\s*\n\s*\}/g, '}');
|
||||
|
||||
// remove duplicated momentum logic in pointerup
|
||||
code = code.replace(/(\s*\/\/ 释放时触发滚行动量\s*if \(Math\.abs\(touchScrollVelocity\) > 0\.2 && touchGateScrollLike && !hasActiveNativeSelectionInTerminal\(\)\) \{\s*touchScrollVelocity \*= 1\.35;\s*runTouchScrollMomentum\(\);\s*\})\s*\/\/ 释放时触发滚行动量\s*if \(Math\.abs\(touchScrollVelocity\) > 0\.2 && touchGateScrollLike && !hasActiveNativeSelectionInTerminal\(\)\) \{\s*touchScrollVelocity \*= 1\.35;\s*runTouchScrollMomentum\(\);\s*\}/g, '$1');
|
||||
|
||||
// fix the syntax bug around touch end momentum
|
||||
code = code.replace(/\s*if \(Math\.abs\(touchScrollVelocity\) > 0\.2\) \{\s*touchScrollVelocity \*= 1\.35;\s*runTouchScrollMomentum\(\);\s*\};\s*\n/g, '\n if (Math.abs(touchScrollVelocity) > 0.2) {\n touchScrollVelocity *= 1.35;\n runTouchScrollMomentum();\n }\n');
|
||||
|
||||
fs.writeFileSync('src/components/TerminalPanel.vue', code);
|
||||
23
pxterm/scripts/archive/cjs_tools/test_global_capture.cjs
Normal file
23
pxterm/scripts/archive/cjs_tools/test_global_capture.cjs
Normal file
@@ -0,0 +1,23 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
|
||||
|
||||
const globalTracker = `
|
||||
// --- 极致全局监听器,抓出是谁吃掉了 move ---
|
||||
const globalEventDebug = (e: Event) => {
|
||||
// 若这是消失的 move 事件,打出到底在哪被拦截了
|
||||
if (e.type === "touchmove" || e.type === "pointermove" || e.type === "touchcancel" || e.type === "pointercancel") {
|
||||
// 为了不刷屏,如果是 pointermove 我们只在 touch 活跃时打印
|
||||
if (e.type === "pointermove" && (e as PointerEvent).pointerType !== "touch") return;
|
||||
|
||||
console.log(\`[GLOBAL INTERCEPT] \${e.type} | Phase: \${e.eventPhase} | Cancelable: \${e.cancelable} | Target: \${(e.target as Element)?.className || (e.target as Element)?.tagName}\`);
|
||||
}
|
||||
};
|
||||
|
||||
['touchmove', 'touchstart', 'touchend', 'touchcancel', 'pointerdown', 'pointermove', 'pointerup', 'pointercancel'].forEach(type => {
|
||||
// 用最顶级的 window capture 拦截,这样在任何人(包括 xterm 内部那些黑盒代码)前面执行
|
||||
window.addEventListener(type, globalEventDebug, { capture: true, passive: false });
|
||||
});
|
||||
`;
|
||||
|
||||
code = code.replace(/function initTerminal\(\)\: void \{/, `function initTerminal(): void {\n${globalTracker}`);
|
||||
fs.writeFileSync('src/components/TerminalPanel.vue', code);
|
||||
17
pxterm/scripts/archive/cjs_tools/use_native.cjs
Normal file
17
pxterm/scripts/archive/cjs_tools/use_native.cjs
Normal file
@@ -0,0 +1,17 @@
|
||||
const fs = require('fs');
|
||||
let code = fs.readFileSync('src/components/TerminalPanel.vue', 'utf8');
|
||||
|
||||
const regexTouchMove = /onTouchKeyboardTouchMove = \(event: TouchEvent\) => \{[\s\S]*?touchScrollLastTime = now;\n\s*\};/g;
|
||||
|
||||
const newTouchMove = `onTouchKeyboardTouchMove = (event: TouchEvent) => {
|
||||
// 通过 stopImmediatePropagation 拦截 xterm 内部那些“接管浏览器滚动”的事件处理器。
|
||||
// 但绝对不要自己掉用 event.preventDefault(),从而把物理滚动和动量动画原模原样还给浏览器与操作系统。
|
||||
event.stopImmediatePropagation();
|
||||
};`;
|
||||
|
||||
code = code.replace(regexTouchMove, newTouchMove);
|
||||
|
||||
// Also remove touchstart redundant vars
|
||||
code = code.replace(/touchScrollLastY = event\.touches\[0\]\?\.clientY \|\| 0;\n\s*touchScrollLastTime = performance\.now\(\);\n/g, '');
|
||||
|
||||
fs.writeFileSync('src/components/TerminalPanel.vue', code);
|
||||
Reference in New Issue
Block a user