From 2436deb3bf42c007b9314098abc12dea62bea4a8 Mon Sep 17 00:00:00 2001 From: Steffen Becker Date: Thu, 20 Aug 2026 00:13:44 +0200 Subject: [PATCH] style: Shape Format follows the caret, and carries vertical text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- src/lib/components/ribbon/Ribbon.svelte | 12 ++++++++++-- src/lib/components/ribbon/tabs/FrameTabs.svelte | 16 +++++++++++++++- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/lib/components/ribbon/Ribbon.svelte b/src/lib/components/ribbon/Ribbon.svelte index 1dd70df..68a3a42 100644 --- a/src/lib/components/ribbon/Ribbon.svelte +++ b/src/lib/components/ribbon/Ribbon.svelte @@ -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 | 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} diff --git a/src/lib/components/ribbon/tabs/FrameTabs.svelte b/src/lib/components/ribbon/tabs/FrameTabs.svelte index 499feff..b36c87b 100644 --- a/src/lib/components/ribbon/tabs/FrameTabs.svelte +++ b/src/lib/components/ribbon/tabs/FrameTabs.svelte @@ -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 @@ /> + +
+ + + editor?.chain().focus().setTextBoxAttrs({ textVertical: !textVertical }).run()} + /> + {/if}