mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-16 13:05:53 +00:00
300 lines
No EOL
10 KiB
HTML
300 lines
No EOL
10 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
|
|
<title>Mobile Text Selection Test</title>
|
|
|
|
<style>
|
|
body {
|
|
font-family: monospace;
|
|
padding: 20px;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.test-text {
|
|
background: #f0f0f0;
|
|
padding: 20px;
|
|
margin: 10px 0;
|
|
border-radius: 5px;
|
|
}
|
|
|
|
.selectable {
|
|
user-select: text;
|
|
-webkit-user-select: text;
|
|
-webkit-touch-callout: default;
|
|
}
|
|
|
|
.non-selectable {
|
|
user-select: none;
|
|
-webkit-user-select: none;
|
|
-webkit-touch-callout: none;
|
|
}
|
|
|
|
button {
|
|
padding: 10px;
|
|
margin: 5px;
|
|
font-size: 16px;
|
|
}
|
|
|
|
#log {
|
|
background: #000;
|
|
color: #0f0;
|
|
padding: 10px;
|
|
height: 200px;
|
|
overflow-y: auto;
|
|
font-family: monospace;
|
|
font-size: 12px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>Mobile Text Selection Test</h1>
|
|
|
|
<div class="test-text selectable" id="test1">
|
|
This text should be selectable normally. Try long pressing on it.
|
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
|
You should see selection handles and context menu.
|
|
</div>
|
|
|
|
<div class="test-text non-selectable" id="test2">
|
|
This text is NOT selectable by default (user-select: none).
|
|
We'll try to programmatically select it on long press.
|
|
</div>
|
|
|
|
<button onclick="testProgrammaticSelection()">Test Programmatic Selection</button>
|
|
<button onclick="testTouchEvents()">Test Touch Events</button>
|
|
<button onclick="testMouseEvents()">Test Mouse Events</button>
|
|
<button onclick="clearSelection()">Clear Selection</button>
|
|
<button onclick="selectAll()">Select All</button>
|
|
|
|
<div id="log"></div>
|
|
|
|
<script>
|
|
function log(message) {
|
|
const logDiv = document.getElementById('log');
|
|
logDiv.innerHTML += new Date().toLocaleTimeString() + ': ' + message + '\n';
|
|
logDiv.scrollTop = logDiv.scrollHeight;
|
|
}
|
|
|
|
function testProgrammaticSelection() {
|
|
const element = document.getElementById('test2');
|
|
|
|
// Temporarily enable selection
|
|
element.style.userSelect = 'text';
|
|
element.style.webkitUserSelect = 'text';
|
|
element.style.webkitTouchCallout = 'default';
|
|
|
|
try {
|
|
const range = document.createRange();
|
|
const selection = window.getSelection();
|
|
|
|
selection.removeAllRanges();
|
|
|
|
if (element.firstChild) {
|
|
range.setStart(element.firstChild, 5);
|
|
range.setEnd(element.firstChild, 20);
|
|
selection.addRange(range);
|
|
|
|
log('Created programmatic selection: "' + selection.toString() + '"');
|
|
log('Range count: ' + selection.rangeCount);
|
|
|
|
// Try to trigger selection UI
|
|
setTimeout(() => {
|
|
// Method 1: Focus the element
|
|
element.focus();
|
|
log('Focused element');
|
|
|
|
// Method 2: Dispatch selectstart event
|
|
const selectStartEvent = new Event('selectstart', { bubbles: true });
|
|
element.dispatchEvent(selectStartEvent);
|
|
log('Dispatched selectstart event');
|
|
|
|
// Method 3: Dispatch selectionchange on document
|
|
const selectionChangeEvent = new Event('selectionchange');
|
|
document.dispatchEvent(selectionChangeEvent);
|
|
log('Dispatched selectionchange event');
|
|
|
|
}, 100);
|
|
|
|
} else {
|
|
log('No text node found');
|
|
}
|
|
} catch (error) {
|
|
log('Error: ' + error.message);
|
|
}
|
|
}
|
|
|
|
function clearSelection() {
|
|
const selection = window.getSelection();
|
|
selection.removeAllRanges();
|
|
log('Cleared selection');
|
|
}
|
|
|
|
function testTouchEvents() {
|
|
const element = document.getElementById('test2');
|
|
|
|
// Enable selection
|
|
element.style.userSelect = 'text';
|
|
element.style.webkitUserSelect = 'text';
|
|
element.style.webkitTouchCallout = 'default';
|
|
|
|
const rect = element.getBoundingClientRect();
|
|
const x = rect.left + rect.width / 2;
|
|
const y = rect.top + rect.height / 2;
|
|
|
|
try {
|
|
// Create touch events
|
|
const touchStart = new TouchEvent('touchstart', {
|
|
bubbles: true,
|
|
cancelable: true,
|
|
touches: [new Touch({
|
|
identifier: 1,
|
|
target: element,
|
|
clientX: x,
|
|
clientY: y
|
|
})]
|
|
});
|
|
|
|
element.dispatchEvent(touchStart);
|
|
log('Dispatched touchstart event');
|
|
|
|
// Hold for long press duration
|
|
setTimeout(() => {
|
|
const touchEnd = new TouchEvent('touchend', {
|
|
bubbles: true,
|
|
cancelable: true,
|
|
changedTouches: [new Touch({
|
|
identifier: 1,
|
|
target: element,
|
|
clientX: x,
|
|
clientY: y
|
|
})]
|
|
});
|
|
|
|
element.dispatchEvent(touchEnd);
|
|
log('Dispatched touchend after 600ms');
|
|
}, 600);
|
|
|
|
} catch (error) {
|
|
log('Touch events error: ' + error.message);
|
|
}
|
|
}
|
|
|
|
function testMouseEvents() {
|
|
const element = document.getElementById('test2');
|
|
|
|
// Enable selection
|
|
element.style.userSelect = 'text';
|
|
element.style.webkitUserSelect = 'text';
|
|
element.style.webkitTouchCallout = 'default';
|
|
|
|
const rect = element.getBoundingClientRect();
|
|
const x = rect.left + rect.width / 2;
|
|
const y = rect.top + rect.height / 2;
|
|
|
|
try {
|
|
// Mouse down
|
|
const mouseDown = new MouseEvent('mousedown', {
|
|
clientX: x,
|
|
clientY: y,
|
|
bubbles: true,
|
|
cancelable: true
|
|
});
|
|
|
|
element.dispatchEvent(mouseDown);
|
|
log('Dispatched mousedown');
|
|
|
|
// Small mouse move to start selection
|
|
setTimeout(() => {
|
|
const mouseMove = new MouseEvent('mousemove', {
|
|
clientX: x + 5,
|
|
clientY: y,
|
|
bubbles: true,
|
|
cancelable: true
|
|
});
|
|
|
|
element.dispatchEvent(mouseMove);
|
|
log('Dispatched mousemove');
|
|
|
|
// Mouse up
|
|
setTimeout(() => {
|
|
const mouseUp = new MouseEvent('mouseup', {
|
|
clientX: x + 5,
|
|
clientY: y,
|
|
bubbles: true,
|
|
cancelable: true
|
|
});
|
|
|
|
element.dispatchEvent(mouseUp);
|
|
log('Dispatched mouseup');
|
|
}, 100);
|
|
}, 50);
|
|
|
|
} catch (error) {
|
|
log('Mouse events error: ' + error.message);
|
|
}
|
|
}
|
|
|
|
function selectAll() {
|
|
const element = document.getElementById('test1');
|
|
|
|
try {
|
|
const range = document.createRange();
|
|
const selection = window.getSelection();
|
|
|
|
selection.removeAllRanges();
|
|
range.selectNodeContents(element);
|
|
selection.addRange(range);
|
|
|
|
log('Selected all content in test1');
|
|
} catch (error) {
|
|
log('Error: ' + error.message);
|
|
}
|
|
}
|
|
|
|
// Long press detection
|
|
let longPressTimer = null;
|
|
let startX, startY;
|
|
|
|
document.getElementById('test2').addEventListener('touchstart', function(e) {
|
|
startX = e.touches[0].clientX;
|
|
startY = e.touches[0].clientY;
|
|
|
|
longPressTimer = setTimeout(() => {
|
|
log('Long press detected on test2');
|
|
testProgrammaticSelection();
|
|
}, 500);
|
|
|
|
log('Touch start on test2');
|
|
});
|
|
|
|
document.getElementById('test2').addEventListener('touchmove', function(e) {
|
|
const deltaX = Math.abs(e.touches[0].clientX - startX);
|
|
const deltaY = Math.abs(e.touches[0].clientY - startY);
|
|
|
|
if (deltaX > 5 || deltaY > 5) {
|
|
clearTimeout(longPressTimer);
|
|
log('Touch moved, cancelling long press');
|
|
}
|
|
});
|
|
|
|
document.getElementById('test2').addEventListener('touchend', function(e) {
|
|
clearTimeout(longPressTimer);
|
|
log('Touch end on test2');
|
|
});
|
|
|
|
// Log selection changes
|
|
document.addEventListener('selectionchange', function() {
|
|
const selection = window.getSelection();
|
|
if (selection.toString()) {
|
|
log('Selection changed: "' + selection.toString() + '"');
|
|
} else {
|
|
log('Selection cleared');
|
|
}
|
|
});
|
|
|
|
log('Mobile selection test loaded');
|
|
</script>
|
|
</body>
|
|
</html> |