diff --git a/.changeset/chat-tables-horizontal-scroll.md b/.changeset/chat-tables-horizontal-scroll.md new file mode 100644 index 0000000..700dd78 --- /dev/null +++ b/.changeset/chat-tables-horizontal-scroll.md @@ -0,0 +1,5 @@ +--- +"@jmfederico/pi-web": patch +--- + +Let chat markdown tables keep their natural width and scroll horizontally instead of being squeezed into the chat column, making them readable on mobile. diff --git a/src/client/src/components/shared.ts b/src/client/src/components/shared.ts index e8a490e..fc603f4 100644 --- a/src/client/src/components/shared.ts +++ b/src/client/src/components/shared.ts @@ -465,8 +465,8 @@ export const chatStyles = css` export const formattedTextStyles = css` :host { display: block; } .formatted { white-space: normal; overflow-wrap: anywhere; line-height: 1.45; text-align: start; unicode-bidi: plaintext; } - p, ul, ol, pre, blockquote, table, .code-block-wrapper { margin: 0 0 10px; } - :is(p, ul, ol, pre, blockquote, table, .code-block-wrapper):last-child { margin-bottom: 0; } + p, ul, ol, pre, blockquote, .table-scroll, .code-block-wrapper { margin: 0 0 10px; } + :is(p, ul, ol, pre, blockquote, .table-scroll, .code-block-wrapper):last-child { margin-bottom: 0; } ul, ol { padding-left: 22px; } li + li { margin-top: 3px; } code { border: 1px solid var(--pi-border); border-radius: 4px; background: var(--pi-bg); padding: 1px 4px; font: 13px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; direction: ltr; text-align: left; unicode-bidi: isolate; } @@ -484,8 +484,10 @@ export const formattedTextStyles = css` h2 { font-size: 17px; } h3 { font-size: 15px; } h4 { font-size: 14px; } - table { border-collapse: collapse; display: block; overflow-x: auto; overflow-y: hidden; } - th, td { border: 1px solid var(--pi-border); padding: 4px 8px; } + .table-scroll { max-width: 100%; overflow-x: auto; overflow-y: hidden; overscroll-behavior-x: contain; -webkit-overflow-scrolling: touch; } + .table-scroll:focus-visible { outline: 1px solid var(--pi-accent); outline-offset: 2px; } + table { border-collapse: collapse; width: max-content; min-width: 100%; max-width: none; } + th, td { border: 1px solid var(--pi-border); padding: 4px 8px; max-width: 48ch; overflow-wrap: anywhere; } th { background: var(--pi-surface); } `; diff --git a/src/client/src/formatting/markdown.ts b/src/client/src/formatting/markdown.ts index 99c5eb7..d7be881 100644 --- a/src/client/src/formatting/markdown.ts +++ b/src/client/src/formatting/markdown.ts @@ -26,6 +26,8 @@ function escapeHtml(text: string): string { .replaceAll(">", ">"); } +const TABLE_SCROLL_CLASS = "table-scroll"; + function sanitizeHtml(html: string): string { const template = document.createElement("template"); template.innerHTML = html; @@ -41,9 +43,25 @@ function sanitizeHtml(html: string): string { element.setAttribute("rel", "noreferrer noopener"); } }); + wrapTablesInScrollRegions(template.content); return template.innerHTML; } +// Markdown tables stay at their natural width and scroll horizontally instead of +// being squeezed into the chat column, which is unreadable on narrow screens. +function wrapTablesInScrollRegions(root: DocumentFragment): void { + root.querySelectorAll("table").forEach((table) => { + if (table.parentElement?.classList.contains(TABLE_SCROLL_CLASS) === true) return; + const wrapper = document.createElement("div"); + wrapper.className = TABLE_SCROLL_CLASS; + wrapper.setAttribute("role", "region"); + wrapper.setAttribute("aria-label", "Table"); + wrapper.setAttribute("tabindex", "0"); + table.before(wrapper); + wrapper.append(table); + }); +} + function isSafeUrl(url: string): boolean { if (url.startsWith("#") || url.startsWith("/")) return true; try {