Files
edentext/src/lib/editor/TableToolbar.svelte
T

171 lines
5.4 KiB
Svelte

<script lang="ts">
import type { Editor, ChainedCommands } from '@tiptap/core';
let {
editor,
top,
left,
}: {
editor: Editor | null;
top: number;
left: number;
} = $props();
// Run a table command without disturbing the cell selection: preventDefault on
// mousedown keeps focus/selection in the editor, then .focus() in the chain
// re-anchors to that selection so the row/column lands at the active cell.
function run(cmd: (chain: ChainedCommands) => ChainedCommands) {
if (!editor) return;
cmd(editor.chain().focus()).run();
}
</script>
<div
class="table-toolbar"
style="top: {top}px; left: {left}px;"
role="toolbar"
tabindex="-1"
aria-label="Table editing"
onmousedown={(e) => e.preventDefault()}
>
<button
class="tt-btn"
title="Insert row above"
aria-label="Insert row above"
onclick={() => run((c) => c.addRowBefore())}
>
<svg width="16" height="16" viewBox="0 0 18 18" fill="none" aria-hidden="true">
<rect x="3" y="9" width="12" height="6" rx="1" stroke="currentColor" stroke-width="1.3"/>
<line x1="9" y1="9" x2="9" y2="15" stroke="currentColor" stroke-width="1.1"/>
<path d="M9 2v4M7 4l2-2 2 2" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<button
class="tt-btn"
title="Insert row below"
aria-label="Insert row below"
onclick={() => run((c) => c.addRowAfter())}
>
<svg width="16" height="16" viewBox="0 0 18 18" fill="none" aria-hidden="true">
<rect x="3" y="3" width="12" height="6" rx="1" stroke="currentColor" stroke-width="1.3"/>
<line x1="9" y1="3" x2="9" y2="9" stroke="currentColor" stroke-width="1.1"/>
<path d="M9 16v-4M7 14l2 2 2-2" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<button
class="tt-btn"
title="Insert column left"
aria-label="Insert column left"
onclick={() => run((c) => c.addColumnBefore())}
>
<svg width="16" height="16" viewBox="0 0 18 18" fill="none" aria-hidden="true">
<rect x="9" y="3" width="6" height="12" rx="1" stroke="currentColor" stroke-width="1.3"/>
<line x1="9" y1="9" x2="15" y2="9" stroke="currentColor" stroke-width="1.1"/>
<path d="M2 9h4M4 7l-2 2 2 2" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<button
class="tt-btn"
title="Insert column right"
aria-label="Insert column right"
onclick={() => run((c) => c.addColumnAfter())}
>
<svg width="16" height="16" viewBox="0 0 18 18" fill="none" aria-hidden="true">
<rect x="3" y="3" width="6" height="12" rx="1" stroke="currentColor" stroke-width="1.3"/>
<line x1="9" y1="9" x2="3" y2="9" stroke="currentColor" stroke-width="1.1"/>
<path d="M16 9h-4M14 7l2 2-2 2" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<span class="tt-sep"></span>
<button
class="tt-btn tt-danger"
title="Delete row"
aria-label="Delete row"
onclick={() => run((c) => c.deleteRow())}
>
<svg width="16" height="16" viewBox="0 0 18 18" fill="none" aria-hidden="true">
<rect x="3" y="6" width="12" height="6" rx="1" stroke="currentColor" stroke-width="1.3"/>
<line x1="6" y1="9" x2="12" y2="9" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/>
</svg>
</button>
<button
class="tt-btn tt-danger"
title="Delete column"
aria-label="Delete column"
onclick={() => run((c) => c.deleteColumn())}
>
<svg width="16" height="16" viewBox="0 0 18 18" fill="none" aria-hidden="true">
<rect x="6" y="3" width="6" height="12" rx="1" stroke="currentColor" stroke-width="1.3"/>
<line x1="9" y1="6" x2="9" y2="12" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/>
</svg>
</button>
<button
class="tt-btn tt-danger"
title="Delete table"
aria-label="Delete table"
onclick={() => run((c) => c.deleteTable())}
>
<svg width="16" height="16" viewBox="0 0 18 18" fill="none" aria-hidden="true">
<path d="M3.5 5h11M7 5V3.5h4V5M5 5l.7 9.5a1 1 0 0 0 1 .9h4.6a1 1 0 0 0 1-.9L13 5" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</div>
<style>
.table-toolbar {
position: absolute;
/* Sit just above the table's top-left corner. */
transform: translateY(calc(-100% - 6px));
z-index: 150;
display: flex;
align-items: center;
gap: 1px;
padding: 3px;
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.14);
/* The toolbar is a sibling of the zoomed .paper, so it renders at a constant
size regardless of editor zoom. */
}
.tt-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 1.7rem;
height: 1.7rem;
min-width: unset;
padding: 0;
border: none;
border-radius: calc(var(--radius) - 2px);
background: transparent;
color: var(--color-text);
cursor: pointer;
transition: background 0.12s, color 0.12s;
}
.tt-btn:hover {
background: var(--color-btn-hover);
}
.tt-danger:hover {
background: #fdecea;
color: #c0392b;
}
.tt-sep {
width: 1px;
height: 1.1rem;
margin: 0 2px;
background: var(--color-border);
}
</style>