mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-22 11:42:30 +03:00
EZQMS-381 Table rows / columns drag and drop (#4176)
Signed-off-by: Alexander Onnikov <alexander.onnikov@xored.com>
This commit is contained in:
parent
4c5d490228
commit
d54422d737
@ -104,21 +104,22 @@
|
||||
.table-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
margin: 1.25rem 0;
|
||||
padding: 1.25rem 0;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -1.25rem;
|
||||
bottom: -1.25rem;
|
||||
left: -1.25rem;
|
||||
right: -1.25rem;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
&.table-selected {
|
||||
&::before {
|
||||
border: 1.25rem var(--theme-button-default) solid;
|
||||
border-radius: 1.25rem;
|
||||
inset: 0 -1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
@ -137,7 +138,7 @@
|
||||
}
|
||||
|
||||
&__row {
|
||||
bottom: -1.25rem;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
|
@ -0,0 +1,81 @@
|
||||
//
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { Node as ProseMirrorNode } from '@tiptap/pm/model'
|
||||
import type { Transaction } from '@tiptap/pm/state'
|
||||
import { TableMap } from '@tiptap/pm/tables'
|
||||
import type { TableNodeLocation } from '../types'
|
||||
|
||||
type TableRow = Array<ProseMirrorNode | null>
|
||||
type TableRows = TableRow[]
|
||||
|
||||
export function moveColumn (table: TableNodeLocation, from: number, to: number, tr: Transaction): Transaction {
|
||||
const cols = transpose(tableToCells(table))
|
||||
moveRowInplace(cols, from, to)
|
||||
tableFromCells(table, transpose(cols), tr)
|
||||
return tr
|
||||
}
|
||||
|
||||
export function moveRow (table: TableNodeLocation, from: number, to: number, tr: Transaction): Transaction {
|
||||
const rows = tableToCells(table)
|
||||
moveRowInplace(rows, from, to)
|
||||
tableFromCells(table, rows, tr)
|
||||
return tr
|
||||
}
|
||||
|
||||
function moveRowInplace (rows: TableRows, from: number, to: number): void {
|
||||
rows.splice(to, 0, rows.splice(from, 1)[0])
|
||||
}
|
||||
|
||||
function transpose (rows: TableRows): TableRows {
|
||||
return rows[0].map((_, colIdx) => rows.map((row) => row[colIdx]))
|
||||
}
|
||||
|
||||
function tableToCells (table: TableNodeLocation): TableRows {
|
||||
const { map, width, height } = TableMap.get(table.node)
|
||||
|
||||
const rows = []
|
||||
for (let row = 0; row < height; row++) {
|
||||
const cells = []
|
||||
for (let col = 0; col < width; col++) {
|
||||
const pos = map[row * width + col]
|
||||
cells.push(table.node.nodeAt(pos))
|
||||
}
|
||||
rows.push(cells)
|
||||
}
|
||||
|
||||
return rows
|
||||
}
|
||||
|
||||
function tableFromCells (table: TableNodeLocation, rows: TableRows, tr: Transaction): void {
|
||||
const { map, width, height } = TableMap.get(table.node)
|
||||
const mapStart = tr.mapping.maps.length
|
||||
|
||||
for (let row = 0; row < height; row++) {
|
||||
for (let col = 0; col < width; col++) {
|
||||
const pos = map[row * width + col]
|
||||
|
||||
const oldCell = table.node.nodeAt(pos)
|
||||
const newCell = rows[row][col]
|
||||
|
||||
if (oldCell !== null && newCell !== null && oldCell !== newCell) {
|
||||
const start = tr.mapping.slice(mapStart).map(table.start + pos)
|
||||
const end = start + oldCell.nodeSize
|
||||
|
||||
tr.replaceWith(start, end, newCell)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,139 @@
|
||||
//
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { type Editor } from '@tiptap/core'
|
||||
import { type EditorState } from '@tiptap/pm/state'
|
||||
import { TableMap } from '@tiptap/pm/tables'
|
||||
import { Decoration } from '@tiptap/pm/view'
|
||||
|
||||
import { type TableNodeLocation } from '../types'
|
||||
import { isColumnSelected, selectColumn } from '../utils'
|
||||
|
||||
import { moveColumn } from './actions'
|
||||
import { handleSvg } from './icons'
|
||||
import {
|
||||
dropMarkerWidthPx,
|
||||
getColDragMarker,
|
||||
getDropMarker,
|
||||
hideDragMarker,
|
||||
hideDropMarker,
|
||||
updateColDropMarker,
|
||||
updateColDragMarker
|
||||
} from './tableDragMarkerDecoration'
|
||||
import { getTableCellWidgetDecorationPos, getTableWidthPx } from './utils'
|
||||
|
||||
interface TableColumn {
|
||||
leftPx: number
|
||||
widthPx: number
|
||||
}
|
||||
|
||||
export const columnHandlerDecoration = (state: EditorState, table: TableNodeLocation, editor: Editor): Decoration[] => {
|
||||
const decorations: Decoration[] = []
|
||||
|
||||
const tableMap = TableMap.get(table.node)
|
||||
for (let col = 0; col < tableMap.width; col++) {
|
||||
const pos = getTableCellWidgetDecorationPos(table, tableMap, col)
|
||||
|
||||
const handle = document.createElement('div')
|
||||
handle.classList.add('table-col-handle')
|
||||
if (isColumnSelected(col, state.selection)) {
|
||||
handle.classList.add('table-col-handle__selected')
|
||||
}
|
||||
handle.innerHTML = handleSvg
|
||||
handle.addEventListener('mousedown', (e) => {
|
||||
handleMouseDown(col, table, e, editor)
|
||||
})
|
||||
decorations.push(Decoration.widget(pos, handle))
|
||||
}
|
||||
|
||||
return decorations
|
||||
}
|
||||
|
||||
const handleMouseDown = (col: number, table: TableNodeLocation, event: MouseEvent, editor: Editor): void => {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
|
||||
// select column
|
||||
editor.view.dispatch(selectColumn(table, col, editor.state.tr))
|
||||
|
||||
// drag column
|
||||
const tableWidthPx = getTableWidthPx(table, editor)
|
||||
const columns = getTableColumns(table, editor)
|
||||
|
||||
let dropIndex = col
|
||||
const startLeft = columns[col].leftPx ?? 0
|
||||
const startX = event.clientX
|
||||
|
||||
const dropMarker = getDropMarker()
|
||||
const dragMarker = getColDragMarker()
|
||||
|
||||
function handleFinish (): void {
|
||||
if (dropMarker !== null) hideDropMarker(dropMarker)
|
||||
if (dragMarker !== null) hideDragMarker(dragMarker)
|
||||
|
||||
if (col !== dropIndex) {
|
||||
let tr = editor.state.tr
|
||||
tr = selectColumn(table, dropIndex, tr)
|
||||
tr = moveColumn(table, col, dropIndex, tr)
|
||||
editor.view.dispatch(tr)
|
||||
}
|
||||
window.removeEventListener('mouseup', handleFinish)
|
||||
window.removeEventListener('mousemove', handleMove)
|
||||
}
|
||||
|
||||
function handleMove (event: MouseEvent): void {
|
||||
if (dropMarker !== null && dragMarker !== null) {
|
||||
const currentLeft = startLeft + event.clientX - startX
|
||||
dropIndex = calculateColumnDropIndex(col, columns, currentLeft)
|
||||
|
||||
const dragMarkerWidthPx = columns[col].widthPx
|
||||
const dragMarkerLeftPx = Math.max(0, Math.min(currentLeft, tableWidthPx - dragMarkerWidthPx))
|
||||
const dropMarkerLeftPx =
|
||||
dropIndex <= col ? columns[dropIndex].leftPx : columns[dropIndex].leftPx + columns[dropIndex].widthPx
|
||||
|
||||
updateColDropMarker(dropMarker, dropMarkerLeftPx - dropMarkerWidthPx / 2, dropMarkerWidthPx)
|
||||
updateColDragMarker(dragMarker, dragMarkerLeftPx, dragMarkerWidthPx)
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('mouseup', handleFinish)
|
||||
window.addEventListener('mousemove', handleMove)
|
||||
}
|
||||
|
||||
function calculateColumnDropIndex (col: number, columns: TableColumn[], left: number): number {
|
||||
const colCenterPx = left + columns[col].widthPx / 2
|
||||
const index = columns.findIndex((p) => colCenterPx < p.leftPx + p.widthPx / 2)
|
||||
return index !== -1 ? (index > col ? index - 1 : index) : columns.length - 1
|
||||
}
|
||||
|
||||
function getTableColumns (table: TableNodeLocation, editor: Editor): TableColumn[] {
|
||||
const result = []
|
||||
let leftPx = 0
|
||||
|
||||
const { map, width } = TableMap.get(table.node)
|
||||
for (let col = 0; col < width; col++) {
|
||||
const dom = editor.view.domAtPos(table.start + map[col] + 1)
|
||||
if (dom.node instanceof HTMLElement) {
|
||||
if (col === 0) {
|
||||
leftPx = dom.node.offsetLeft
|
||||
}
|
||||
result.push({
|
||||
leftPx: dom.node.offsetLeft - leftPx,
|
||||
widthPx: dom.node.offsetWidth
|
||||
})
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
//
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { type Editor } from '@tiptap/core'
|
||||
import { type EditorState } from '@tiptap/pm/state'
|
||||
import { TableMap } from '@tiptap/pm/tables'
|
||||
import { Decoration } from '@tiptap/pm/view'
|
||||
|
||||
import { addSvg } from './icons'
|
||||
import { type TableNodeLocation } from '../types'
|
||||
import { insertColumn, isColumnSelected } from '../utils'
|
||||
|
||||
import { getTableCellWidgetDecorationPos, getTableHeightPx } from './utils'
|
||||
|
||||
export const columnInsertDecoration = (state: EditorState, table: TableNodeLocation, editor: Editor): Decoration[] => {
|
||||
const decorations: Decoration[] = []
|
||||
|
||||
const { selection } = state
|
||||
|
||||
const tableMap = TableMap.get(table.node)
|
||||
const { width } = tableMap
|
||||
|
||||
const tableHeightPx = getTableHeightPx(table, editor)
|
||||
|
||||
for (let col = 0; col < width; col++) {
|
||||
const show = col < width - 1 && !isColumnSelected(col, selection) && !isColumnSelected(col + 1, selection)
|
||||
|
||||
if (show) {
|
||||
const insert = document.createElement('div')
|
||||
insert.classList.add('table-col-insert')
|
||||
|
||||
const button = document.createElement('button')
|
||||
button.className = 'table-insert-button'
|
||||
button.innerHTML = addSvg
|
||||
button.addEventListener('mousedown', (e) => {
|
||||
handleMouseDown(col, table, e, editor)
|
||||
})
|
||||
insert.appendChild(button)
|
||||
|
||||
const marker = document.createElement('div')
|
||||
marker.className = 'table-insert-marker'
|
||||
marker.style.height = tableHeightPx + 'px'
|
||||
insert.appendChild(marker)
|
||||
|
||||
const pos = getTableCellWidgetDecorationPos(table, tableMap, col)
|
||||
decorations.push(Decoration.widget(pos, insert))
|
||||
}
|
||||
}
|
||||
|
||||
return decorations
|
||||
}
|
||||
|
||||
const handleMouseDown = (col: number, table: TableNodeLocation, event: Event, editor: Editor): void => {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
|
||||
editor.view.dispatch(insertColumn(table, col + 1, editor.state.tr))
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
//
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { type Editor } from '@tiptap/core'
|
||||
import { type EditorState } from '@tiptap/pm/state'
|
||||
import { TableMap } from '@tiptap/pm/tables'
|
||||
import { Decoration } from '@tiptap/pm/view'
|
||||
|
||||
import { type TableNodeLocation } from '../types'
|
||||
import { isRowSelected, selectRow } from '../utils'
|
||||
|
||||
import { moveRow } from './actions'
|
||||
import { handleSvg } from './icons'
|
||||
import {
|
||||
dropMarkerWidthPx,
|
||||
getDropMarker,
|
||||
getRowDragMarker,
|
||||
hideDragMarker,
|
||||
hideDropMarker,
|
||||
updateRowDropMarker,
|
||||
updateRowDragMarker
|
||||
} from './tableDragMarkerDecoration'
|
||||
import { getTableCellWidgetDecorationPos, getTableHeightPx } from './utils'
|
||||
|
||||
interface TableRow {
|
||||
topPx: number
|
||||
heightPx: number
|
||||
}
|
||||
|
||||
export const rowHandlerDecoration = (state: EditorState, table: TableNodeLocation, editor: Editor): Decoration[] => {
|
||||
const decorations: Decoration[] = []
|
||||
|
||||
const tableMap = TableMap.get(table.node)
|
||||
for (let row = 0; row < tableMap.height; row++) {
|
||||
const pos = getTableCellWidgetDecorationPos(table, tableMap, row * tableMap.width)
|
||||
|
||||
const handle = document.createElement('div')
|
||||
handle.classList.add('table-row-handle')
|
||||
if (isRowSelected(row, state.selection)) {
|
||||
handle.classList.add('table-row-handle__selected')
|
||||
}
|
||||
handle.innerHTML = handleSvg
|
||||
handle.addEventListener('mousedown', (e) => {
|
||||
handleMouseDown(row, table, e, editor)
|
||||
})
|
||||
decorations.push(Decoration.widget(pos, handle))
|
||||
}
|
||||
|
||||
return decorations
|
||||
}
|
||||
|
||||
const handleMouseDown = (row: number, table: TableNodeLocation, event: MouseEvent, editor: Editor): void => {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
|
||||
// select row
|
||||
editor.view.dispatch(selectRow(table, row, editor.state.tr))
|
||||
|
||||
// drag row
|
||||
const tableHeightPx = getTableHeightPx(table, editor)
|
||||
const rows = getTableRows(table, editor)
|
||||
|
||||
let dropIndex = row
|
||||
const startTop = rows[row].topPx ?? 0
|
||||
const startY = event.clientY
|
||||
|
||||
const dropMarker = getDropMarker()
|
||||
const dragMarker = getRowDragMarker()
|
||||
|
||||
function handleFinish (): void {
|
||||
if (dropMarker !== null) hideDropMarker(dropMarker)
|
||||
if (dragMarker !== null) hideDragMarker(dragMarker)
|
||||
|
||||
if (row !== dropIndex) {
|
||||
let tr = editor.state.tr
|
||||
tr = selectRow(table, dropIndex, tr)
|
||||
tr = moveRow(table, row, dropIndex, tr)
|
||||
editor.view.dispatch(tr)
|
||||
}
|
||||
window.removeEventListener('mouseup', handleFinish)
|
||||
window.removeEventListener('mousemove', handleMove)
|
||||
}
|
||||
|
||||
function handleMove (event: MouseEvent): void {
|
||||
if (dropMarker !== null && dragMarker !== null) {
|
||||
const cursorTop = startTop + event.clientY - startY
|
||||
dropIndex = calculateRowDropIndex(row, rows, cursorTop)
|
||||
|
||||
const dragMarkerHeightPx = rows[row].heightPx
|
||||
const dragMarkerTopPx = Math.max(0, Math.min(cursorTop, tableHeightPx - dragMarkerHeightPx))
|
||||
const dropMarkerTopPx =
|
||||
dropIndex <= row ? rows[dropIndex].topPx : rows[dropIndex].topPx + rows[dropIndex].heightPx
|
||||
|
||||
updateRowDropMarker(dropMarker, dropMarkerTopPx - dropMarkerWidthPx / 2, dropMarkerWidthPx)
|
||||
updateRowDragMarker(dragMarker, dragMarkerTopPx, dragMarkerHeightPx)
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('mouseup', handleFinish)
|
||||
window.addEventListener('mousemove', handleMove)
|
||||
}
|
||||
|
||||
function calculateRowDropIndex (row: number, rows: TableRow[], top: number): number {
|
||||
const rowCenterPx = top + rows[row].heightPx / 2
|
||||
const index = rows.findIndex((p) => rowCenterPx <= p.topPx + p.heightPx)
|
||||
return index !== -1 ? (index > row ? index - 1 : index) : rows.length - 1
|
||||
}
|
||||
|
||||
function getTableRows (table: TableNodeLocation, editor: Editor): TableRow[] {
|
||||
const result = []
|
||||
let topPx = 0
|
||||
|
||||
const { map, height } = TableMap.get(table.node)
|
||||
for (let row = 0; row < height; row++) {
|
||||
const dom = editor.view.domAtPos(table.start + map[row] + 1)
|
||||
if (dom.node instanceof HTMLElement) {
|
||||
const heightPx = dom.node.offsetHeight
|
||||
result.push({ topPx, heightPx })
|
||||
topPx += heightPx
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
//
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { type Editor } from '@tiptap/core'
|
||||
import { type EditorState } from '@tiptap/pm/state'
|
||||
import { TableMap } from '@tiptap/pm/tables'
|
||||
import { Decoration } from '@tiptap/pm/view'
|
||||
|
||||
import { addSvg } from './icons'
|
||||
import { type TableNodeLocation } from '../types'
|
||||
import { insertRow, isRowSelected } from '../utils'
|
||||
|
||||
import { getTableCellWidgetDecorationPos, getTableWidthPx } from './utils'
|
||||
|
||||
export const rowInsertDecoration = (state: EditorState, table: TableNodeLocation, editor: Editor): Decoration[] => {
|
||||
const decorations: Decoration[] = []
|
||||
|
||||
const { selection } = state
|
||||
|
||||
const tableMap = TableMap.get(table.node)
|
||||
const { height } = tableMap
|
||||
|
||||
const tableWidthPx = getTableWidthPx(table, editor)
|
||||
|
||||
for (let row = 0; row < height; row++) {
|
||||
const show = row < height - 1 && !isRowSelected(row, selection) && !isRowSelected(row + 1, selection)
|
||||
|
||||
if (show) {
|
||||
const dot = document.createElement('div')
|
||||
dot.classList.add('table-row-insert')
|
||||
|
||||
const button = document.createElement('button')
|
||||
button.className = 'table-insert-button'
|
||||
button.innerHTML = addSvg
|
||||
button.addEventListener('mousedown', (e) => {
|
||||
handleMouseDown(row, table, e, editor)
|
||||
})
|
||||
dot.appendChild(button)
|
||||
|
||||
const marker = document.createElement('div')
|
||||
marker.className = 'table-insert-marker'
|
||||
marker.style.width = tableWidthPx + 'px'
|
||||
dot.appendChild(marker)
|
||||
|
||||
const pos = getTableCellWidgetDecorationPos(table, tableMap, row * tableMap.width)
|
||||
decorations.push(Decoration.widget(pos, dot))
|
||||
}
|
||||
}
|
||||
|
||||
return decorations
|
||||
}
|
||||
|
||||
const handleMouseDown = (row: number, table: TableNodeLocation, event: Event, editor: Editor): void => {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
|
||||
editor.view.dispatch(insertRow(table, row + 1, editor.state.tr))
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
//
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { type EditorState } from '@tiptap/pm/state'
|
||||
import { Decoration } from '@tiptap/pm/view'
|
||||
|
||||
import { handleSvg } from './icons'
|
||||
import { type TableNodeLocation } from '../types'
|
||||
|
||||
export const dropMarkerId = 'table-drop-marker'
|
||||
export const colDragMarkerId = 'table-col-drag-marker'
|
||||
export const rowDragMarkerId = 'table-row-drag-marker'
|
||||
|
||||
export const dropMarkerWidthPx = 2
|
||||
|
||||
export const tableDragMarkerDecoration = (state: EditorState, table: TableNodeLocation): Decoration[] => {
|
||||
const dropMarker = document.createElement('div')
|
||||
dropMarker.id = dropMarkerId
|
||||
dropMarker.classList.add('table-drop-marker')
|
||||
|
||||
const colDragMarker = document.createElement('div')
|
||||
colDragMarker.id = colDragMarkerId
|
||||
colDragMarker.classList.add('table-col-drag-marker')
|
||||
colDragMarker.innerHTML = handleSvg
|
||||
colDragMarker.style.display = 'none'
|
||||
|
||||
const rowDragMarker = document.createElement('div')
|
||||
rowDragMarker.id = rowDragMarkerId
|
||||
rowDragMarker.classList.add('table-row-drag-marker')
|
||||
rowDragMarker.innerHTML = handleSvg
|
||||
rowDragMarker.style.display = 'none'
|
||||
|
||||
return [
|
||||
Decoration.widget(table.start, dropMarker),
|
||||
Decoration.widget(table.start, colDragMarker),
|
||||
Decoration.widget(table.start, rowDragMarker)
|
||||
]
|
||||
}
|
||||
|
||||
export type DropMarkerHTMLElement = HTMLElement
|
||||
|
||||
export function getDropMarker (): DropMarkerHTMLElement | null {
|
||||
return document.getElementById(dropMarkerId) as DropMarkerHTMLElement
|
||||
}
|
||||
|
||||
export function hideDropMarker (element: DropMarkerHTMLElement): void {
|
||||
element.style.display = 'none'
|
||||
}
|
||||
|
||||
export function updateColDropMarker (element: DropMarkerHTMLElement, left: number, width: number): void {
|
||||
element.style.height = '100%'
|
||||
element.style.width = `${width}px`
|
||||
element.style.top = '0'
|
||||
element.style.left = `${left}px`
|
||||
element.style.display = 'block'
|
||||
}
|
||||
|
||||
export function updateRowDropMarker (element: DropMarkerHTMLElement, top: number, height: number): void {
|
||||
element.style.width = '100%'
|
||||
element.style.height = `${height}px`
|
||||
element.style.left = '0'
|
||||
element.style.top = `${top}px`
|
||||
element.style.display = 'block'
|
||||
}
|
||||
|
||||
export type DragMarkerHTMLElement = HTMLElement
|
||||
|
||||
export function getColDragMarker (): DragMarkerHTMLElement | null {
|
||||
return document.getElementById(colDragMarkerId) as DragMarkerHTMLElement
|
||||
}
|
||||
|
||||
export function getRowDragMarker (): DragMarkerHTMLElement | null {
|
||||
return document.getElementById(rowDragMarkerId) as DragMarkerHTMLElement
|
||||
}
|
||||
|
||||
export function getDragMarker (element: DragMarkerHTMLElement): void {
|
||||
element.style.display = 'none'
|
||||
}
|
||||
|
||||
export function hideDragMarker (element: DragMarkerHTMLElement): void {
|
||||
element.style.display = 'none'
|
||||
}
|
||||
|
||||
export function updateColDragMarker (element: DragMarkerHTMLElement, left: number, width: number): void {
|
||||
element.style.width = `${width}px`
|
||||
element.style.left = `${left}px`
|
||||
element.style.display = 'block'
|
||||
}
|
||||
|
||||
export function updateRowDragMarker (element: DragMarkerHTMLElement, top: number, height: number): void {
|
||||
element.style.height = `${height}px`
|
||||
element.style.top = `${top}px`
|
||||
element.style.display = 'block'
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
//
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { type EditorState } from '@tiptap/pm/state'
|
||||
import { CellSelection, TableMap } from '@tiptap/pm/tables'
|
||||
import { Decoration } from '@tiptap/pm/view'
|
||||
|
||||
import { type TableNodeLocation } from '../types'
|
||||
|
||||
export const tableSelectionDecoration = (state: EditorState, table: TableNodeLocation): Decoration[] => {
|
||||
const decorations: Decoration[] = []
|
||||
|
||||
const { selection } = state
|
||||
|
||||
const tableMap = TableMap.get(table.node)
|
||||
|
||||
if (selection instanceof CellSelection) {
|
||||
const selected: number[] = []
|
||||
|
||||
selection.forEachCell((_node, pos) => {
|
||||
const start = pos - table.pos - 1
|
||||
selected.push(start)
|
||||
})
|
||||
|
||||
selection.forEachCell((node, pos) => {
|
||||
const start = pos - table.pos - 1
|
||||
const borders = getTableCellBorders(start, selected, tableMap)
|
||||
|
||||
const classes = ['table-cell-selected']
|
||||
|
||||
if (borders.top) classes.push('table-cell-selected__border-top')
|
||||
if (borders.bottom) classes.push('table-cell-selected__border-bottom')
|
||||
if (borders.left) classes.push('table-cell-selected__border-left')
|
||||
if (borders.right) classes.push('table-cell-selected__border-right')
|
||||
|
||||
decorations.push(Decoration.node(pos, pos + node.nodeSize, { class: classes.join(' ') }))
|
||||
})
|
||||
}
|
||||
|
||||
return decorations
|
||||
}
|
||||
|
||||
function getTableCellBorders (
|
||||
cell: number,
|
||||
selection: number[],
|
||||
tableMap: TableMap
|
||||
): { top: boolean, bottom: boolean, left: boolean, right: boolean } {
|
||||
const { width, height } = tableMap
|
||||
const cellIndex = tableMap.map.indexOf(cell)
|
||||
|
||||
const topCell = cellIndex >= width ? tableMap.map[cellIndex - width] : undefined
|
||||
const bottomCell = cellIndex < width * height - width ? tableMap.map[cellIndex + width] : undefined
|
||||
const leftCell = cellIndex % width !== 0 ? tableMap.map[cellIndex - 1] : undefined
|
||||
const rightCell = cellIndex % width !== width - 1 ? tableMap.map[cellIndex + 1] : undefined
|
||||
|
||||
return {
|
||||
top: topCell === undefined || !selection.includes(topCell),
|
||||
bottom: bottomCell === undefined || !selection.includes(bottomCell),
|
||||
left: leftCell === undefined || !selection.includes(leftCell),
|
||||
right: rightCell === undefined || !selection.includes(rightCell)
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
//
|
||||
// Copyright © 2023 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { type Editor } from '@tiptap/core'
|
||||
import { type TableMap } from '@tiptap/pm/tables'
|
||||
import { type TableNodeLocation } from '../types'
|
||||
|
||||
export function getTableCellWidgetDecorationPos (table: TableNodeLocation, map: TableMap, index: number): number {
|
||||
return table.start + map.map[index] + 1
|
||||
}
|
||||
|
||||
export function getTableHeightPx (table: TableNodeLocation, editor: Editor): number {
|
||||
const dom = editor.view.domAtPos(table.start)
|
||||
return dom.node.parentElement?.offsetHeight ?? 0
|
||||
}
|
||||
|
||||
export function getTableWidthPx (table: TableNodeLocation, editor: Editor): number {
|
||||
const dom = editor.view.domAtPos(table.start)
|
||||
return dom.node.parentElement?.offsetWidth ?? 0
|
||||
}
|
@ -15,13 +15,16 @@
|
||||
|
||||
import { type Editor } from '@tiptap/core'
|
||||
import TiptapTableCell from '@tiptap/extension-table-cell'
|
||||
import { type EditorState, Plugin, PluginKey, type Selection } from '@tiptap/pm/state'
|
||||
import { CellSelection, TableMap } from '@tiptap/pm/tables'
|
||||
import { Decoration, DecorationSet } from '@tiptap/pm/view'
|
||||
import { Plugin, PluginKey, type Selection } from '@tiptap/pm/state'
|
||||
import { DecorationSet } from '@tiptap/pm/view'
|
||||
|
||||
import { addSvg, handleSvg } from './icons'
|
||||
import { type TableNodeLocation } from './types'
|
||||
import { insertColumn, insertRow, findTable, isColumnSelected, isRowSelected, selectColumn, selectRow } from './utils'
|
||||
import { findTable } from './utils'
|
||||
import { columnHandlerDecoration } from './decorations/columnHandlerDecoration'
|
||||
import { columnInsertDecoration } from './decorations/columnInsertDecoration'
|
||||
import { rowInsertDecoration } from './decorations/rowInsertDecoration'
|
||||
import { tableDragMarkerDecoration } from './decorations/tableDragMarkerDecoration'
|
||||
import { tableSelectionDecoration } from './decorations/tableSelectionDecoration'
|
||||
import { rowHandlerDecoration } from './decorations/rowHandlerDecoration'
|
||||
|
||||
export const TableCell = TiptapTableCell.extend({
|
||||
addProseMirrorPlugins () {
|
||||
@ -58,11 +61,12 @@ const tableCellDecorationPlugin = (editor: Editor): Plugin<TableCellDecorationPl
|
||||
}
|
||||
|
||||
const decorations = DecorationSet.create(newState.doc, [
|
||||
...tableSelectionDecoration(newState, newTable),
|
||||
...tableDragMarkerDecoration(newState, newTable),
|
||||
...columnHandlerDecoration(newState, newTable, editor),
|
||||
...columnInsertDecoration(newState, newTable, editor),
|
||||
...rowHandlerDecoration(newState, newTable, editor),
|
||||
...rowInsertDecoration(newState, newTable, editor),
|
||||
...selectionDecoration(newState, newTable)
|
||||
...rowInsertDecoration(newState, newTable, editor)
|
||||
])
|
||||
return { selection: newState.selection, decorations }
|
||||
}
|
||||
@ -74,216 +78,3 @@ const tableCellDecorationPlugin = (editor: Editor): Plugin<TableCellDecorationPl
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const columnHandlerDecoration = (state: EditorState, table: TableNodeLocation, editor: Editor): Decoration[] => {
|
||||
const decorations: Decoration[] = []
|
||||
|
||||
const { selection } = state
|
||||
|
||||
const tableMap = TableMap.get(table.node)
|
||||
for (let col = 0; col < tableMap.width; col++) {
|
||||
const pos = getTableCellWidgetDecorationPos(table, tableMap, col)
|
||||
|
||||
const handle = document.createElement('div')
|
||||
handle.classList.add('table-col-handle')
|
||||
if (isColumnSelected(col, selection)) {
|
||||
handle.classList.add('table-col-handle__selected')
|
||||
}
|
||||
handle.innerHTML = handleSvg
|
||||
handle.addEventListener('mousedown', (e) => {
|
||||
handleColHandleMouseDown(col, table, e, editor)
|
||||
})
|
||||
decorations.push(Decoration.widget(pos, handle))
|
||||
}
|
||||
|
||||
return decorations
|
||||
}
|
||||
|
||||
const columnInsertDecoration = (state: EditorState, table: TableNodeLocation, editor: Editor): Decoration[] => {
|
||||
const decorations: Decoration[] = []
|
||||
|
||||
const { selection } = state
|
||||
|
||||
const tableMap = TableMap.get(table.node)
|
||||
const { width } = tableMap
|
||||
|
||||
const dom = editor.view.domAtPos(table.start)
|
||||
const tableHeightPx = dom.node.parentElement?.clientHeight ?? 0
|
||||
|
||||
for (let col = 0; col < width; col++) {
|
||||
const show = col < width - 1 && !isColumnSelected(col, selection) && !isColumnSelected(col + 1, selection)
|
||||
|
||||
if (show) {
|
||||
const insert = document.createElement('div')
|
||||
insert.classList.add('table-col-insert')
|
||||
|
||||
const button = document.createElement('button')
|
||||
button.className = 'table-insert-button'
|
||||
button.innerHTML = addSvg
|
||||
button.addEventListener('mousedown', (e) => {
|
||||
handleColInsertMouseDown(col, table, e, editor)
|
||||
})
|
||||
insert.appendChild(button)
|
||||
|
||||
const marker = document.createElement('div')
|
||||
marker.className = 'table-insert-marker'
|
||||
marker.style.height = tableHeightPx + 'px'
|
||||
insert.appendChild(marker)
|
||||
|
||||
const pos = getTableCellWidgetDecorationPos(table, tableMap, col)
|
||||
decorations.push(Decoration.widget(pos, insert))
|
||||
}
|
||||
}
|
||||
|
||||
return decorations
|
||||
}
|
||||
|
||||
const handleColHandleMouseDown = (col: number, table: TableNodeLocation, event: Event, editor: Editor): void => {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
|
||||
editor.view.dispatch(selectColumn(table, col, editor.state.tr))
|
||||
}
|
||||
|
||||
const handleColInsertMouseDown = (col: number, table: TableNodeLocation, event: Event, editor: Editor): void => {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
|
||||
editor.view.dispatch(insertColumn(table, col + 1, editor.state.tr))
|
||||
}
|
||||
|
||||
const rowHandlerDecoration = (state: EditorState, table: TableNodeLocation, editor: Editor): Decoration[] => {
|
||||
const decorations: Decoration[] = []
|
||||
|
||||
const { selection } = state
|
||||
|
||||
const tableMap = TableMap.get(table.node)
|
||||
for (let row = 0; row < tableMap.height; row++) {
|
||||
const pos = getTableCellWidgetDecorationPos(table, tableMap, row * tableMap.width)
|
||||
|
||||
const handle = document.createElement('div')
|
||||
handle.classList.add('table-row-handle')
|
||||
if (isRowSelected(row, selection)) {
|
||||
handle.classList.add('table-row-handle__selected')
|
||||
}
|
||||
handle.innerHTML = handleSvg
|
||||
handle.addEventListener('mousedown', (e) => {
|
||||
handleRowHandleMouseDown(row, table, e, editor)
|
||||
})
|
||||
decorations.push(Decoration.widget(pos, handle))
|
||||
}
|
||||
|
||||
return decorations
|
||||
}
|
||||
|
||||
const rowInsertDecoration = (state: EditorState, table: TableNodeLocation, editor: Editor): Decoration[] => {
|
||||
const decorations: Decoration[] = []
|
||||
|
||||
const { selection } = state
|
||||
|
||||
const tableMap = TableMap.get(table.node)
|
||||
const { height } = tableMap
|
||||
|
||||
const dom = editor.view.domAtPos(table.start)
|
||||
const tableWidthPx = dom.node.parentElement?.clientWidth ?? 0
|
||||
|
||||
for (let row = 0; row < height; row++) {
|
||||
const show = row < height - 1 && !isRowSelected(row, selection) && !isRowSelected(row + 1, selection)
|
||||
|
||||
if (show) {
|
||||
const dot = document.createElement('div')
|
||||
dot.classList.add('table-row-insert')
|
||||
|
||||
const button = document.createElement('button')
|
||||
button.className = 'table-insert-button'
|
||||
button.innerHTML = addSvg
|
||||
button.addEventListener('mousedown', (e) => {
|
||||
handleRowInsertMouseDown(row, table, e, editor)
|
||||
})
|
||||
dot.appendChild(button)
|
||||
|
||||
const marker = document.createElement('div')
|
||||
marker.className = 'table-insert-marker'
|
||||
marker.style.width = tableWidthPx + 'px'
|
||||
dot.appendChild(marker)
|
||||
|
||||
const pos = getTableCellWidgetDecorationPos(table, tableMap, row * tableMap.width)
|
||||
decorations.push(Decoration.widget(pos, dot))
|
||||
}
|
||||
}
|
||||
|
||||
return decorations
|
||||
}
|
||||
|
||||
const handleRowHandleMouseDown = (row: number, table: TableNodeLocation, event: Event, editor: Editor): void => {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
|
||||
editor.view.dispatch(selectRow(table, row, editor.state.tr))
|
||||
}
|
||||
|
||||
const handleRowInsertMouseDown = (row: number, table: TableNodeLocation, event: Event, editor: Editor): void => {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
|
||||
editor.view.dispatch(insertRow(table, row + 1, editor.state.tr))
|
||||
}
|
||||
|
||||
const selectionDecoration = (state: EditorState, table: TableNodeLocation): Decoration[] => {
|
||||
const decorations: Decoration[] = []
|
||||
|
||||
const { selection } = state
|
||||
|
||||
const tableMap = TableMap.get(table.node)
|
||||
|
||||
if (selection instanceof CellSelection) {
|
||||
const selected: number[] = []
|
||||
|
||||
selection.forEachCell((_node, pos) => {
|
||||
const start = pos - table.pos - 1
|
||||
selected.push(start)
|
||||
})
|
||||
|
||||
selection.forEachCell((node, pos) => {
|
||||
const start = pos - table.pos - 1
|
||||
const borders = getTableCellBorders(start, selected, tableMap)
|
||||
|
||||
const classes = ['table-cell-selected']
|
||||
|
||||
if (borders.top) classes.push('table-cell-selected__border-top')
|
||||
if (borders.bottom) classes.push('table-cell-selected__border-bottom')
|
||||
if (borders.left) classes.push('table-cell-selected__border-left')
|
||||
if (borders.right) classes.push('table-cell-selected__border-right')
|
||||
|
||||
decorations.push(Decoration.node(pos, pos + node.nodeSize, { class: classes.join(' ') }))
|
||||
})
|
||||
}
|
||||
|
||||
return decorations
|
||||
}
|
||||
|
||||
function getTableCellWidgetDecorationPos (table: TableNodeLocation, map: TableMap, index: number): number {
|
||||
const pos = table.node.resolve(map.map[index] + 1)
|
||||
return table.start + pos.start()
|
||||
}
|
||||
|
||||
function getTableCellBorders (
|
||||
cell: number,
|
||||
selection: number[],
|
||||
tableMap: TableMap
|
||||
): { top: boolean, bottom: boolean, left: boolean, right: boolean } {
|
||||
const { width, height } = tableMap
|
||||
const cellIndex = tableMap.map.indexOf(cell)
|
||||
|
||||
const topCell = cellIndex >= width ? tableMap.map[cellIndex - width] : undefined
|
||||
const bottomCell = cellIndex < width * height - width ? tableMap.map[cellIndex + width] : undefined
|
||||
const leftCell = cellIndex % width !== 0 ? tableMap.map[cellIndex - 1] : undefined
|
||||
const rightCell = cellIndex % width !== width - 1 ? tableMap.map[cellIndex + 1] : undefined
|
||||
|
||||
return {
|
||||
top: topCell === undefined || !selection.includes(topCell),
|
||||
bottom: bottomCell === undefined || !selection.includes(bottomCell),
|
||||
left: leftCell === undefined || !selection.includes(leftCell),
|
||||
right: rightCell === undefined || !selection.includes(rightCell)
|
||||
}
|
||||
}
|
||||
|
@ -298,6 +298,7 @@
|
||||
|
||||
--text-editor-toc-default-color: rgba(255, 255, 255, 0.1);
|
||||
--text-editor-toc-hovered-color: rgba(255, 255, 255, 0.4);
|
||||
--text-editor-drag-marker-bg-color: #444248;
|
||||
|
||||
--theme-clockface-back: radial-gradient(farthest-corner at 50% 0%, #bbb, #fff 100%);
|
||||
--theme-clockface-shadow: inset 0 -3px 10px #aaa;
|
||||
@ -524,6 +525,7 @@
|
||||
|
||||
--text-editor-toc-default-color: rgba(0, 0, 0, 0.1);
|
||||
--text-editor-toc-hovered-color: rgba(0, 0, 0, 0.4);
|
||||
--text-editor-drag-marker-bg-color: #444248;
|
||||
|
||||
--theme-clockface-back: radial-gradient(farthest-corner at 50% 0%, #606060, #000 100%);
|
||||
--theme-clockface-shadow: inset 0 -3px 10px #000;
|
||||
|
@ -15,14 +15,21 @@
|
||||
|
||||
/* Table */
|
||||
table.proseTable {
|
||||
--table-selection-border-indent: -0.125rem;
|
||||
--table-selection-border-radius: 0.125rem;
|
||||
--table-selection-border-width: 0.125rem;
|
||||
--table-selection-border-width: 2px;
|
||||
--table-selection-border-indent: -2px;
|
||||
--table-selection-border-radius: 2px;
|
||||
--table-selection-border-width: 2px;
|
||||
--table-handle-size: 1.25rem;
|
||||
--table-handle-indent: calc(-1.25rem - 1px);
|
||||
|
||||
--table-selection-z-index: 100;
|
||||
--table-drag-and-drop-z-index: 110;
|
||||
--table-handlers-z-index: 120;
|
||||
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
|
||||
td,
|
||||
@ -59,7 +66,7 @@ table.proseTable {
|
||||
border-radius: var(--table-selection-border-radius);
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
z-index: 110;
|
||||
z-index: var(--table-selection-z-index);
|
||||
top: var(--table-selection-border-indent);
|
||||
bottom: var(--table-selection-border-indent);
|
||||
left: var(--table-selection-border-indent);
|
||||
@ -91,7 +98,6 @@ table.proseTable {
|
||||
svg {
|
||||
color: var(--theme-button-contrast-hovered);
|
||||
opacity: 0;
|
||||
z-index: 120;
|
||||
}
|
||||
|
||||
&__selected {
|
||||
@ -102,7 +108,7 @@ table.proseTable {
|
||||
border-radius: var(--table-selection-border-radius);
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
z-index: 110;
|
||||
z-index: var(--table-handlers-z-index);
|
||||
top: var(--table-selection-border-indent);
|
||||
bottom: var(--table-selection-border-indent);
|
||||
left: var(--table-selection-border-indent);
|
||||
@ -110,6 +116,7 @@ table.proseTable {
|
||||
}
|
||||
|
||||
svg {
|
||||
z-index: var(--table-handlers-z-index);
|
||||
color: white;
|
||||
opacity: 1;
|
||||
}
|
||||
@ -119,8 +126,8 @@ table.proseTable {
|
||||
.table-col-handle {
|
||||
position: absolute;
|
||||
width: calc(100% + 1px);
|
||||
height: 1.25rem;
|
||||
top: calc(-1.25rem - 1px);
|
||||
height: var(--table-handle-size);
|
||||
top: var(--table-handle-indent);
|
||||
left: 0;
|
||||
|
||||
&:hover {
|
||||
@ -134,18 +141,21 @@ table.proseTable {
|
||||
&__selected {
|
||||
&::before {
|
||||
right: -1px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
border-bottom-width: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-row-handle {
|
||||
position: absolute;
|
||||
width: 1.25rem;
|
||||
width: var(--table-handle-size);
|
||||
height: calc(100% + 1px);
|
||||
top: 0;
|
||||
left: calc(-1.25rem - 1px);
|
||||
left: var(--table-handle-indent);
|
||||
|
||||
svg {
|
||||
transform: rotate(90deg);
|
||||
@ -164,8 +174,11 @@ table.proseTable {
|
||||
&__selected {
|
||||
&::before {
|
||||
bottom: -1px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border-right-width: 0;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -183,7 +196,7 @@ table.proseTable {
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
top: -1.25rem;
|
||||
top: var(--table-handle-indent);
|
||||
right: -0.625rem;
|
||||
width: 1.25rem;
|
||||
|
||||
@ -197,7 +210,7 @@ table.proseTable {
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
left: -1.25rem;
|
||||
left: var(--table-handle-indent);
|
||||
bottom: -0.625rem;
|
||||
height: 1.25rem;
|
||||
|
||||
@ -241,6 +254,50 @@ table.proseTable {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.table-drop-marker {
|
||||
background-color: var(--primary-button-focused);
|
||||
position: absolute;
|
||||
z-index: var(--table-drag-and-drop-z-index);
|
||||
}
|
||||
|
||||
.table-col-drag-marker,
|
||||
.table-row-drag-marker {
|
||||
position: absolute;
|
||||
z-index: var(--table-drag-and-drop-z-index);
|
||||
opacity: 0.5;
|
||||
background-color: var(--text-editor-drag-marker-bg-color);
|
||||
border: var(--table-selection-border-width) solid var(--text-editor-drag-marker-bg-color);
|
||||
border-radius: var(--table-selection-border-radius);
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
svg {
|
||||
color: white;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.table-col-drag-marker {
|
||||
height: var(--table-handle-size);
|
||||
top: calc(var(--table-handle-indent) + 1px);
|
||||
|
||||
svg {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.table-row-drag-marker {
|
||||
width: var(--table-handle-size);
|
||||
left: calc(var(--table-handle-indent) + 1px / 2);
|
||||
|
||||
svg {
|
||||
height: 100%;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.proseCode {
|
||||
|
Loading…
Reference in New Issue
Block a user