From ad3f0507fdced34d555bb2b81d777187fe47865b Mon Sep 17 00:00:00 2001 From: Steffen Becker Date: Wed, 12 Aug 2026 01:54:49 +0200 Subject: [PATCH] feat: the ribbon's contextual tabs for a table, a picture and a shape MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit They appear while the caret is in the object and go with it, handing the strip back to Home. Table Design carries the style gallery, shading, borders and the style options; Table Layout the rows, columns, merge, split, alignment — and cell margins, which round-tripped with nothing to set them. The floating toolbars stay: a mini-toolbar beside the object and a contextual tab above the document are both Word's, and both reuse the same pickers. --- src/lib/components/ribbon/Ribbon.svelte | 66 +++- src/lib/components/ribbon/icons.ts | 22 ++ .../components/ribbon/tabs/FrameTabs.svelte | 146 +++++++++ .../components/ribbon/tabs/TableTabs.svelte | 295 ++++++++++++++++++ src/lib/i18n/locales/de.ts | 8 + src/lib/i18n/locales/en.ts | 8 + 6 files changed, 542 insertions(+), 3 deletions(-) create mode 100644 src/lib/components/ribbon/tabs/FrameTabs.svelte create mode 100644 src/lib/components/ribbon/tabs/TableTabs.svelte diff --git a/src/lib/components/ribbon/Ribbon.svelte b/src/lib/components/ribbon/Ribbon.svelte index 7aa2a9b..52e159a 100644 --- a/src/lib/components/ribbon/Ribbon.svelte +++ b/src/lib/components/ribbon/Ribbon.svelte @@ -1,5 +1,6 @@ + + + {#each WRAPS as w} + setWrap(w.key)} /> + {/each} + + +{#if which === 'shape'} +
+ + + {#each SHAPES as s} + editor?.chain().focus().setTextBoxAttrs({ shapeKind: s.key }).run()} /> + {/each} +
+ editor?.chain().focus().setTextBoxAttrs({ fillColor: c }).run()} + onClear={() => editor?.chain().focus().setTextBoxAttrs({ fillColor: null }).run()} + icon={fillIcon} + /> + {t().textBox.fillColor} +
+
+ editor?.chain().focus().setTextBoxAttrs({ strokeColor: c }).run()} + onClear={() => editor?.chain().focus().setTextBoxAttrs({ strokeColor: null }).run()} + icon={strokeIcon} + /> + {t().textBox.borderColor} +
+ +
+{/if} + +{#snippet fillIcon()}{/snippet} +{#snippet strokeIcon()}{/snippet} + + diff --git a/src/lib/components/ribbon/tabs/TableTabs.svelte b/src/lib/components/ribbon/tabs/TableTabs.svelte new file mode 100644 index 0000000..6be7485 --- /dev/null +++ b/src/lib/components/ribbon/tabs/TableTabs.svelte @@ -0,0 +1,295 @@ + + +{#if which === 'design'} + +
+ + {t().table.tableStyle} +
+
+ +
+ + +
+ editor?.chain().focus().setCellAttribute('backgroundColor', c).run()} + onClear={() => editor?.chain().focus().setCellAttribute('backgroundColor', null).run()} + icon={shadeIcon} + /> + {t().table.cellShading} +
+
+ + {t().borders.title} +
+
+ +
+ + +
+ run((c) => c.toggleHeaderRowStyle())} /> + run((c) => c.toggleHeaderColumnStyle())} /> +
+
+{:else} + +
+ run((c) => c.addRowBefore())} /> + run((c) => c.addRowAfter())} /> +
+
+ run((c) => c.addColumnBefore())} /> + run((c) => c.addColumnAfter())} /> +
+
+ +
+ + +
+ run((c) => c.deleteRow())} /> + run((c) => c.deleteColumn())} /> + run((c) => c.deleteTable())} /> +
+
+ +
+ + + run((c) => c.mergeCells())} /> + + + { splitOpen = false; editor?.chain().focus().splitCellInto(cols, rows).run(); }} + onClose={() => (splitOpen = false)} + /> + + + +
+ + + {#each VALIGNS as v} + editor?.chain().focus().setCellAttribute('verticalAlign', v.key).run()} + /> + {/each} + + +
+ + +
+ toggleMenu('cellMargins')} + onCaret={() => toggleMenu('cellMargins')} + /> + {#if isMenuOpen('cellMargins') && padding} + + {/if} +
+
+{/if} + +{#snippet shadeIcon()}{/snippet} + + diff --git a/src/lib/i18n/locales/de.ts b/src/lib/i18n/locales/de.ts index 69aa146..3ca4d27 100644 --- a/src/lib/i18n/locales/de.ts +++ b/src/lib/i18n/locales/de.ts @@ -476,6 +476,13 @@ const de: Messages = { language: 'Sprache', show: 'Anzeigen', debug: 'Debug', + tableDecor: 'Schattierung & Rahmen', + tableOptions: 'Tabellenformatoptionen', + rowsColumns: 'Zeilen & Spalten', + merge: 'Zusammenführen', + cellSize: 'Zellengröße', + arrange: 'Anordnen', + shapeStyles: 'Formenarten', editing: 'Bearbeiten', }, case: { @@ -524,6 +531,7 @@ const de: Messages = { tocOptions: 'Ebenen', tocMaxLevel: 'Tiefste angezeigte Ebene', debugDump: 'Dump', + cellMargins: 'Zellenränder', chrome: { title: 'Symbolleisten-Stil', ribbon: 'Menüband', diff --git a/src/lib/i18n/locales/en.ts b/src/lib/i18n/locales/en.ts index 8505fe7..b1b233b 100644 --- a/src/lib/i18n/locales/en.ts +++ b/src/lib/i18n/locales/en.ts @@ -480,6 +480,13 @@ const en = { language: 'Language', show: 'Show', debug: 'Debug', + tableDecor: 'Shading & Borders', + tableOptions: 'Table Style Options', + rowsColumns: 'Rows & Columns', + merge: 'Merge', + cellSize: 'Cell Size', + arrange: 'Arrange', + shapeStyles: 'Shape Styles', editing: 'Editing', }, case: { @@ -528,6 +535,7 @@ const en = { tocOptions: 'Levels', tocMaxLevel: 'Deepest level shown', debugDump: 'Dump', + cellMargins: 'Cell margins', chrome: { title: 'Toolbar style', ribbon: 'Ribbon',