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}