style: Shape Format follows the caret, and carries vertical text

The tab only showed while the box was node-selected, so it vanished the
moment you clicked in to type — the floating toolbar stayed. It now reads
findTextBox(), the same box that toolbar reads; an image selected inside a
box still shows Picture Format alone. Vertical text was the one control the
tab was missing, now a Text group of its own.
This commit is contained in:
Steffen Becker committed 2026-08-20 00:23:46 +02:00
1 parent 7b3348ca6e
commit 2436deb3bf
2 files changed
+25 -3

No files matched your search

+10 -2
View File
@@ -19,6 +19,7 @@
import { t } from '../../i18n/i18n.svelte';
import { withShortcut } from '../../i18n/shortcut';
import { shortcutHint } from '../../editor/shortcuts';
import { findTextBox } from '../../editor/extensions/textBox';
import { loadRibbonCollapsed, saveRibbonCollapsed, type ChromeMode, type ThemeMode } from '../../storage/theme';
import type { StyleFamily } from '../../styles/styleSheet';
import { DEFAULT_MARGINS, type PageMargins } from '../../storage/pageMargins';
@@ -146,17 +147,23 @@
return sel instanceof NodeSelection ? sel.node.type.name : null;
});
// A text box is worked on with the caret inside it just as often as with the box
// selected, so the tab follows the caret — the floating toolbar reads the same box.
let textBox = $derived.by(() => (tick >= 0 && editor ? findTextBox(editor.state) : null));
let inTextBox = $derived(!!textBox && selectedNode !== 'image');
let frameAttrs = $derived.by<Record<string, unknown> | null>(() => {
if (tick < 0 || !editor) return null;
const sel = editor.state.selection;
return sel instanceof NodeSelection ? sel.node.attrs : null;
if (sel instanceof NodeSelection && !inTextBox) return sel.node.attrs;
return textBox?.node.attrs ?? null;
});
let shown = $derived([
...TABS,
...(inTable ? (['tableDesign', 'tableLayout'] as const) : []),
...(selectedNode === 'image' ? (['pictureFormat'] as const) : []),
...(selectedNode === 'textBox' ? (['shapeFormat'] as const) : []),
...(inTextBox ? (['shapeFormat'] as const) : []),
] as Tab[]);
$effect(() => {
@@ -360,6 +367,7 @@
fillColor={frameAttrs?.fillColor as string | null}
strokeColor={frameAttrs?.strokeColor as string | null}
strokeWidthPt={frameAttrs?.strokeWidthPt as number}
textVertical={frameAttrs?.textVertical === true}
/>
{/if}
</div>
@@ -12,7 +12,7 @@
// Word's Picture Format and Shape Format: the same wrap modes, plus a shape's
// own fill, outline and kind.
let { editor, which, wrap, alt = '', shapeKind, fillColor, strokeColor, strokeWidthPt }: {
let { editor, which, wrap, alt = '', shapeKind, fillColor, strokeColor, strokeWidthPt, textVertical = false }: {
editor: Editor | null;
which: 'picture' | 'shape';
wrap: WrapMode;
@@ -21,6 +21,7 @@
fillColor?: string | null;
strokeColor?: string | null;
strokeWidthPt?: number;
textVertical?: boolean;
} = $props();
const WRAPS: { key: WrapMode; icon: 'wrapInline' | 'wrapLeft' | 'wrapRight' | 'wrapTopBottom'; label: () => string }[] = [
@@ -129,6 +130,19 @@
/>
</label>
</RibbonGroup>
<div class="ribbon-sep"></div>
<RibbonGroup label={t().ribbon.groups.text}>
<RibbonButton
variant="big"
icon="textDirection"
label={t().textBox.verticalText}
title={t().textBox.verticalText}
active={textVertical}
onclick={() => editor?.chain().focus().setTextBoxAttrs({ textVertical: !textVertical }).run()}
/>
</RibbonGroup>
{/if}
<CaptionDialog bind:open={captionOpen} {editor} />