fix: fix toolbar active states not updating in Svelte 5

This commit is contained in:
Steffen Becker committed 2026-05-25 20:49:45 +02:00
1 parent c3da447674
commit da32b2d773
3 files changed
+26 -14

No files matched your search

+3 -2
View File
@@ -5,6 +5,7 @@
import { exportToOdt } from './lib/export/odt';
let editor: Editor | null = $state(null);
let tick: number = $state(0);
async function handleExport() {
if (editor) await exportToOdt(editor);
@@ -13,12 +14,12 @@
<main>
<header>
<Toolbar {editor} />
<Toolbar {editor} {tick} />
<button class="export-btn" onclick={handleExport} disabled={!editor}>
Download .odt
</button>
</header>
<EditorComponent bind:editor />
<EditorComponent bind:editor bind:tick />
</main>
<style>
+4 -3
View File
@@ -5,7 +5,7 @@
import { saveDocument, loadDocument } from '../storage/autosave';
import '../../styles/editor.css';
let { editor = $bindable() }: { editor: Editor | null } = $props();
let { editor = $bindable(), tick = $bindable(0) }: { editor: Editor | null; tick: number } = $props();
let element: HTMLDivElement;
@@ -17,8 +17,9 @@
extensions,
content: saved || undefined,
onTransaction: () => {
// Force Svelte reactivity update
editor = editor;
// Increment tick to signal Svelte that editor state changed.
// (editor = editor is a no-op in Svelte 5 for same references)
tick++;
},
onUpdate: ({ editor: e }) => {
saveDocument(e.getJSON());
+19 -9
View File
@@ -1,28 +1,38 @@
<script lang="ts">
import type { Editor } from '@tiptap/core';
let { editor }: { editor: Editor | null } = $props();
let { editor, tick }: { editor: Editor | null; tick: number } = $props();
// $derived re-evaluates whenever `tick` changes (i.e. on every TipTap transaction)
let isBold = $derived(tick >= 0 && !!editor?.isActive('bold'));
let isItalic = $derived(tick >= 0 && !!editor?.isActive('italic'));
let isUnderline = $derived(tick >= 0 && !!editor?.isActive('underline'));
let isH1 = $derived(tick >= 0 && !!editor?.isActive('heading', { level: 1 }));
let isH2 = $derived(tick >= 0 && !!editor?.isActive('heading', { level: 2 }));
let isH3 = $derived(tick >= 0 && !!editor?.isActive('heading', { level: 3 }));
let canUndo = $derived(tick >= 0 && !!editor?.can().undo());
let canRedo = $derived(tick >= 0 && !!editor?.can().redo());
</script>
<div class="toolbar">
{#if editor}
<div class="toolbar-group">
<button
class:active={editor.isActive('bold')}
class:active={isBold}
onclick={() => editor?.chain().focus().toggleBold().run()}
title="Bold (Ctrl+B)"
>
<strong>B</strong>
</button>
<button
class:active={editor.isActive('italic')}
class:active={isItalic}
onclick={() => editor?.chain().focus().toggleItalic().run()}
title="Italic (Ctrl+I)"
>
<em>I</em>
</button>
<button
class:active={editor.isActive('underline')}
class:active={isUnderline}
onclick={() => editor?.chain().focus().toggleUnderline().run()}
title="Underline (Ctrl+U)"
>
@@ -34,21 +44,21 @@
<div class="toolbar-group">
<button
class:active={editor.isActive('heading', { level: 1 })}
class:active={isH1}
onclick={() => editor?.chain().focus().toggleHeading({ level: 1 }).run()}
title="Heading 1"
>
H1
</button>
<button
class:active={editor.isActive('heading', { level: 2 })}
class:active={isH2}
onclick={() => editor?.chain().focus().toggleHeading({ level: 2 }).run()}
title="Heading 2"
>
H2
</button>
<button
class:active={editor.isActive('heading', { level: 3 })}
class:active={isH3}
onclick={() => editor?.chain().focus().toggleHeading({ level: 3 }).run()}
title="Heading 3"
>
@@ -61,14 +71,14 @@
<div class="toolbar-group">
<button
onclick={() => editor?.chain().focus().undo().run()}
disabled={!editor.can().undo()}
disabled={!canUndo}
title="Undo (Ctrl+Z)"
>
↩
</button>
<button
onclick={() => editor?.chain().focus().redo().run()}
disabled={!editor.can().redo()}
disabled={!canRedo}
title="Redo (Ctrl+Shift+Z)"
>
↪