Сообщение от MC-XOBAHCK
|
В вашем скрипте случайно нет чего то запрещающего редактирование?
|
Да, точно есть, это как раз начало выделения ячеек.
Сообщение от MC-XOBAHCK
|
1. Если работает ваш скрипт, то таблица становится не редактируемая
|
Добавил
при двойном щелчке редактирование. Во время редактирования ячейки отключается выделение ячеек, которое обратно включается, когда
теряется фокус у таблицы или нажат Esc.
Сообщение от MC-XOBAHCK
|
когда вызываю контекстное меню (клик правая кнопка мыши), то выделение ячеек удаляется.
|
Исправил, т. е. выделение начинается только если основная кнопка мыши нажата.
Двойной щелчёк позволяет начать редактирование, также он является индикатором того, что во время редактирования должен выделятся именно текст, а не начинаться выделение ячеек. Чтобы закончить редактирование, щёлкните вне таблицы или нажмите Esc.
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<style type="text/css">
table {
height: 500px;
width: 500px;
border-collapse: collapse;
position: relative;
user-select: none;
cursor: default;
table-layout: fixed;
font: 1em Helvetica Neue, Segoe UI, Roboto, Ubuntu, sans-serif;
text-align: center;
}
td {
border: 1px gray solid;
}
td:focus {
box-shadow: inset 0 0 0 2px rgba(111, 168, 220, 0.66);
outline: 0;
}
</style>
<script>
class Box {
constructor(x = 0, y = 0, width = 0, height = 0) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
contains({ x, y, width, height }) {
return (
x > this.x &&
y > this.y &&
(x + width) < (this.x + this.width) &&
(y + height) < (this.y + this.height)
);
}
overlaps({ x, y, width, height }) {
return (
1.001 * Math.max(x, this.x) < 0.998 * Math.min(x + width, this.x + this.width) &&
1.001 * Math.max(y, this.y) < 0.998 * Math.min(y + height, this.y + this.height)
);
}
equals({ x, y, width, height }) {
return (
x === this.x &&
y === this.y &&
width === this.width &&
height === this.height
);
}
add({x, y, width, height}) {
return new Box(
Math.min(x, this.x),
Math.min(y, this.y),
Math.max(x + width, this.x + this.width) - Math.min(x, this.x),
Math.max(y + height, this.y + this.height) - Math.min(y, this.y)
);
}
relativeTo({ x, y }) {
return new Box(
this.x - x,
this.y - y,
this.width,
this.height
);
}
relativeToElement(element) {
return this.relativeTo(element.getBoundingClientRect());
}
static from({ x = 0, y = 0, width = 0, height = 0 } = {}) {
return new Box(x, y, width, height);
}
}
class EditableTable extends HTMLTableElement {
constructor() {
super();
const document = this.ownerDocument;
this.selectionElement = document.createElement("div");
this.selectionElement2 = document.createElement("div");
this.selectionElement.style.cssText = `
position: absolute;
background: rgba(111, 168, 220, 0.66);
pointer-events: none;
`;
this.selectionElement2.style.cssText = `
position: absolute;
outline: 1px dashed rgba(0, 0, 0, 0.5);
pointer-events: none;
`;
this._startSelect = this.startSelect.bind(this);
this._moveSelect = this.moveSelect.bind(this);
this._endSelect = this.endSelect.bind(this);
this.addEventListener("dblclick", this.dblClickHandler.bind(this));
this.addEventListener("focus", this.unregisterSelectHandlers.bind(this), true);
this.addEventListener("blur", this.registerSelectHandlers.bind(this), true);
this.addEventListener("keyup", this.registerSelectHandlers.bind(this), true);
this.registerSelectHandlers();
new MutationObserver(mutations => {
for(const mutation of mutations) {
if(mutation.type !== "childList") continue;
for(const node of mutation.addedNodes) {
if(node.constructor !== HTMLTableCellElement) continue;
node.contentEditable = true;
node.tabIndex = 0;
}
}
}).observe(this, { childList: true, subtree: true });
}
dblClickHandler({ target }) {
(target.closest("td") || { focus() {} }).focus();
}
unregisterSelectHandlers(event) {
this.removeEventListener("mousedown", this._startSelect);
document.removeEventListener("mousemove", this._moveSelect);
document.removeEventListener("mouseup", this._endSelect);
}
registerSelectHandlers(event = {}) {
if(event.type === "keyup" && event.keyCode !== 27) return;
if(event.type === "keyup") event.target.blur();
this.addEventListener("mousedown", this._startSelect);
document.addEventListener("mousemove", this._moveSelect);
document.addEventListener("mouseup", this._endSelect);
}
startSelect(event) {
if(event.button !== 0) return;
this._x = event.pageX;
this._y = event.pageY;
this._pointerIsMoving = true;
this.style.cursor = "crosshair";
this.cellBoxes = [];
for(const cell of this.querySelectorAll("td")) {
const box = Box.from(cell.getBoundingClientRect())
.relativeToElement(this);
this.cellBoxes.push(box);
}
this.appendChild(this.selectionElement);
this.appendChild(this.selectionElement2);
this.moveSelect(event);
}
moveSelect(event) {
if(!this._pointerIsMoving) return;
event.preventDefault();
const { pageX: x, pageY: y } = event;
const selectionBox = new Box(
Math.min(x, this._x), Math.min(y, this._y),
Math.abs(x - this._x), Math.abs(y - this._y)
).relativeToElement(this);
let actualSelectionBox = this.constructor.getActualSelection(selectionBox, this.cellBoxes);
this.constructor.transferBoxToElement(actualSelectionBox, this.selectionElement);
this.constructor.transferBoxToElement(selectionBox, this.selectionElement2);
}
endSelect(event) {
if(!this._pointerIsMoving) return;
this._pointerIsMoving = false;
this.style.cursor = "";
const { pageX: x, pageY: y } = event;
this._selectionBox = new Box(
Math.min(x, this._x), Math.min(y, this._y),
Math.abs(x - this._x), Math.abs(y - this._y)
).relativeToElement(this);
this.selectionElement2.remove();
}
static transferBoxToElement({x, y, width, height }, { style }) {
style.left = `${x}px`;
style.top = `${y}px`;
style.width = `${width}px`;
style.height = `${height}px`;
}
static getActualSelection(selectionBox, cellBoxes) {
const box = getActualSelectionHelper(selectionBox, cellBoxes);
return box.equals(getActualSelectionHelper(box, cellBoxes)) ? box : this.getActualSelection(box, cellBoxes);
function getActualSelectionHelper(selectionBox, cellBoxes) {
let actualSelectionBox = Box.from(selectionBox)
for(const box of cellBoxes) {
if(selectionBox.overlaps(box)) {
actualSelectionBox = actualSelectionBox.add(box);
}
}
return actualSelectionBox;
}
}
merge() {
let actualSelectionBox = this.constructor.getActualSelection(this._selectionBox, this.cellBoxes);
this.constructor.transferBoxToElement(actualSelectionBox, this.selectionElement);
this.constructor.transferBoxToElement(this._selectionBox, this.selectionElement2);
const cellsMap = [];
function addToMap(x, y, r = 0) {
loop: for(var j = r; true; j++) {
if(!cellsMap[j]) cellsMap[j] = [];
for(var i = 0; true; i++) {
if(!cellsMap[j][i]) {
for(var v = 0; v < y; v++) {
for(var u = 0; u < x; u++) {
if(!cellsMap[j + v]) cellsMap[j + v] = [];
cellsMap[j + v][i + u] = 1;
}
}
break loop;
}
}
}
}
let selectedCells = [], index;
let isFirstSelectedRowProcessed = false;
for(const row of this.rows) {
for(const cell of row.cells) {
const cellBox = Box.from(cell.getBoundingClientRect()).relativeToElement(this);
if(actualSelectionBox.overlaps(cellBox)) {
if(!isFirstSelectedRowProcessed) {
isFirstSelectedRowProcessed = true;
index = 0;
}
selectedCells.push(cell);
addToMap(cell.colSpan, cell.rowSpan, index);
}
}
index++;
}
const span = {
colSpan: "0" in cellsMap ? cellsMap[0].length : 0,
rowSpan: cellsMap.length
};
const selectedCell = selectedCells.shift();
Object.assign(selectedCell || {}, span);
selectedCells.forEach(cell => {
selectedCell.append(...cell.childNodes);
cell.remove()
});
this.selectionElement.remove();
}
split() {
// TODO если нужно
}
}
customElements.define("editable-table", EditableTable, { extends: "table" });
</script>
</head>
<body>
<!-- метод merge у таблицы работает как обычный метод у элемента: при наличии выделенных ячеек они объединяются -->
<button onclick="document.querySelector('table[is=editable-table]').merge();">Объединить</button>
<table is="editable-table">
<tbody>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
</body>
</html>
Сообщение от MC-XOBAHCK
|
В скрипте ... pointer-events: none;
|
Это чтобы по выделению не попадало, а то как по ячейке тогда получится щёлкнуть.