feat: header row toggle

This commit is contained in:
Steffen Becker committed 2026-06-28 12:23:41 +02:00
1 parent 3cc2e65423
commit 418f152d4b
11 files changed
+312 -25

No files matched your search

+27
View File
@@ -1,6 +1,7 @@
<script lang="ts">
import type { Editor, ChainedCommands } from '@tiptap/core';
import { CellSelection } from '@tiptap/pm/tables';
import { isHeaderRowStyled } from '../editor/extensions/tableHeaderRow';
import ColorPicker from './ColorPicker.svelte';
let {
@@ -28,6 +29,9 @@
// Merge needs a multi-cell selection; re-evaluated per transaction via `tick`.
const canMerge = $derived(tick >= 0 && !!editor && editor.can().mergeCells());
// Whether the first row is styled as a header (bold + light shading).
const isHeaderRow = $derived(tick >= 0 && !!editor && isHeaderRowStyled(editor.state));
// Background colour of the selected cell(s): the uniform value, '' if mixed, null if
// none. Drives the ColorPicker's "current" swatch (re-evaluated per `tick`).
function cellBg(): string | null {
@@ -205,6 +209,24 @@
</svg>
{/snippet}
</ColorPicker>
<span class="tt-sep"></span>
<button
class="tt-btn"
class:active={isHeaderRow}
title="Header row (bold + shading)"
aria-label="Header row"
aria-pressed={isHeaderRow}
onclick={() => run((c) => c.toggleHeaderRowStyle())}
>
<svg width="16" height="16" viewBox="0 0 18 18" fill="none" aria-hidden="true">
<rect x="3" y="3" width="12" height="12" rx="1" stroke="currentColor" stroke-width="1.3"/>
<rect x="3" y="3" width="12" height="4" fill="currentColor" opacity="0.85"/>
<line x1="9" y1="7" x2="9" y2="15" stroke="currentColor" stroke-width="1.1"/>
<line x1="3" y1="11" x2="15" y2="11" stroke="currentColor" stroke-width="1.1"/>
</svg>
</button>
</div>
<style>
@@ -250,6 +272,11 @@
cursor: default;
}
.tt-btn.active {
background: var(--color-btn-hover);
color: var(--color-primary);
}
.tt-danger:hover {
background: #fdecea;
color: #c0392b;
+11 -6
View File
@@ -3,6 +3,7 @@
import HistoryButton from './HistoryButton.svelte';
import AlignButton from './AlignButton.svelte';
import { ORDERED_LIST_TYPES, DEFAULT_ORDERED_TYPE, type OrderedListType } from '../utils/orderedListTypes';
import { isInHeaderCell } from '../editor/extensions/tableHeaderRow';
let { editor, tick }: { editor: Editor | null; tick: number } = $props();
@@ -11,10 +12,14 @@
let isH2 = $derived(tick >= 0 && !!editor?.isActive('heading', { level: 2 }));
let isH3 = $derived(tick >= 0 && !!editor?.isActive('heading', { level: 3 }));
let isHeading = $derived(isH1 || isH2 || isH3);
// fontWeight:'normal' is set explicitly to override heading boldness
// Header-row cells render bold by default (CSS), like headings — so the Bold button
// toggles the fontWeight:'normal' override there too (keeps header bold editable).
let inHeaderCell = $derived(tick >= 0 && !!editor && isInHeaderCell(editor.state));
let boldByDefault = $derived(isHeading || inHeaderCell);
// fontWeight:'normal' is set explicitly to override the default boldness
let hasNormalWeight = $derived(tick >= 0 && !!editor?.isActive('textStyle', { fontWeight: 'normal' }));
// Bold = explicit bold mark, OR inside a heading that hasn't been un-bolded
let isBold = $derived(tick >= 0 && (!!editor?.isActive('bold') || (isHeading && !hasNormalWeight)));
// Bold = explicit bold mark, OR a bold-by-default context that hasn't been un-bolded
let isBold = $derived(tick >= 0 && (!!editor?.isActive('bold') || (boldByDefault && !hasNormalWeight)));
let isItalic = $derived(tick >= 0 && !!editor?.isActive('italic'));
let isUnderline = $derived(tick >= 0 && !!editor?.isActive('underline'));
let isStrike = $derived(tick >= 0 && !!editor?.isActive('strike'));
@@ -59,12 +64,12 @@
<button
class:active={isBold}
onclick={() => {
if (isHeading) {
if (boldByDefault) {
if (hasNormalWeight) {
// Restore heading's natural bold
// Restore the default (heading / header-row) bold
editor?.chain().focus().unsetFontWeight().run();
} else {
// Override heading bold with explicit font-weight: normal
// Override the default bold with explicit font-weight: normal
editor?.chain().focus().setFontWeight('normal').run();
}
} else {