From bd842b87a26feca836a1e64671b4f4301259dc1e Mon Sep 17 00:00:00 2001 From: TypoGenie Date: Wed, 18 Feb 2026 23:46:32 +0200 Subject: [PATCH] a11y: add DOCX metadata, image placeholders, table headers --- src/components/Preview.tsx | 3 ++- src/utils/docxConverter.ts | 30 +++++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/components/Preview.tsx b/src/components/Preview.tsx index 9c14dfa..f955fe2 100644 --- a/src/components/Preview.tsx +++ b/src/components/Preview.tsx @@ -224,7 +224,8 @@ export const Preview: React.FC = ({ palette: template.typography?.colors, id: template.id, page: template.page, - useTableHeaders + useTableHeaders, + inputFileName }); const arrayBuffer = await blob.arrayBuffer(); diff --git a/src/utils/docxConverter.ts b/src/utils/docxConverter.ts index 0f8ec51..1343e28 100644 --- a/src/utils/docxConverter.ts +++ b/src/utils/docxConverter.ts @@ -48,6 +48,7 @@ export interface ConversionOptions { id?: string; page?: any; useTableHeaders?: boolean; + inputFileName?: string; } // Transform text to all caps if needed @@ -622,6 +623,12 @@ export const generateDocxDocument = async ( // Create paragraph for table cell const paragraph = new Paragraph({ children: runs.length > 0 ? runs : [new TextRun({ text: headerEl.textContent || '', font: headerFont, size: headerSize, color: headerColor, bold: true })], + heading: level === 1 ? HeadingLevel.HEADING_1 : + level === 2 ? HeadingLevel.HEADING_2 : + level === 3 ? HeadingLevel.HEADING_3 : + level === 4 ? HeadingLevel.HEADING_4 : + level === 5 ? HeadingLevel.HEADING_5 : + HeadingLevel.HEADING_6, alignment: mapAlignment(cfg.align), spacing: { before: 0, @@ -777,7 +784,8 @@ export const generateDocxDocument = async ( })); } - rows.push(new TableRow({ children: cells })); + const hasThCells = Array.from(rowEl.querySelectorAll('th')).length > 0; + rows.push(new TableRow({ children: cells, tableHeader: hasThCells })); } return new Table({ @@ -1195,6 +1203,23 @@ export const generateDocxDocument = async ( return results; } + // Images - produce accessible placeholder text + if (tag === 'img') { + const alt = el.getAttribute('alt') || ''; + const placeholderText = alt ? `[Image: ${alt}]` : '[Image]'; + results.push(new Paragraph({ + children: [new TextRun({ + text: placeholderText, + font: body.font, + size: pt(body.size), + color: formatColor(resolveColorToHex(body.color) || '666666'), + italics: true, + })], + spacing: { before: 120, after: 120 }, + })); + return results; + } + // HR if (tag === 'hr') { const hrConfig = elements?.hr; @@ -1229,6 +1254,9 @@ export const generateDocxDocument = async ( // Build document options const documentOptions: any = { + title: options.inputFileName || 'Document', + description: 'Generated by TypoGenie', + creator: 'TypoGenie', styles: { default: { document: {