@@ -3,7 +3,6 @@ import { useRef, useState, type ComponentProps } from "react";
33
44import { useCopyToClipboard } from "../../hooks/useCopyToClipboard" ;
55import { getClientSettings } from "../../hooks/useSettings" ;
6- import { observeResize } from "../../lib/observeResize" ;
76import {
87 serializeTableElementToCsv ,
98 serializeTableElementToMarkdown ,
@@ -15,35 +14,9 @@ import { ScrollArea } from "../ui/scroll-area";
1514import { Tooltip , TooltipPopup , TooltipTrigger } from "../ui/tooltip" ;
1615import { reportMarkdownActionFailure } from "./markdownActionFailure" ;
1716
18- /**
19- * Whether any cell is clipped in the unwrapped layout. Only then does the wrap
20- * toggle change anything, so short tables hide it. Measures with the container
21- * briefly unwrapped and restores it before the browser paints.
22- */
23- function tableHasClippedCells ( container : HTMLElement , table : HTMLTableElement ) : boolean {
24- const viewport = table . closest < HTMLElement > ( '[data-slot="scroll-area-viewport"]' ) ;
25- const scrollLeft = viewport ?. scrollLeft ?? 0 ;
26- const previous = container . dataset . expanded ;
27- container . dataset . expanded = "false" ;
28- try {
29- for ( const row of table . rows ) {
30- for ( const cell of row . cells ) {
31- if ( cell . scrollWidth > cell . clientWidth ) return true ;
32- }
33- }
34- return false ;
35- } finally {
36- if ( previous === undefined ) delete container . dataset . expanded ;
37- else container . dataset . expanded = previous ;
38- // The unwrapped layout can be narrower and clamp a sideways scroll.
39- if ( viewport && scrollLeft > 0 ) viewport . scrollLeft = scrollLeft ;
40- }
41- }
42-
4317export function MarkdownTable ( { children, ...props } : ComponentProps < "table" > ) {
4418 const containerRef = useRef < HTMLDivElement | null > ( null ) ;
4519 const [ expanded , setExpanded ] = useState ( ( ) => getClientSettings ( ) . wordWrap ) ;
46- const [ hasClippedCells , setHasClippedCells ] = useState ( false ) ;
4720 const { copyToClipboard, isCopied : copied } = useCopyToClipboard < "markdown" | "csv" > ( {
4821 timeout : 1200 ,
4922 onError : ( cause , format ) =>
@@ -52,25 +25,6 @@ export function MarkdownTable({ children, ...props }: ComponentProps<"table">) {
5225 const expandLabel = expanded ? "Collapse table cells" : "Expand table cells" ;
5326 const copyLabel = copied ? "Copied" : "Copy table" ;
5427
55- // React calls this with null when a previous attach returned no cleanup.
56- const attachContainer = ( container : HTMLDivElement | null ) => {
57- containerRef . current = container ;
58- const table = container ?. querySelector ( "table" ) ;
59- if ( ! container || ! table ) return ;
60- const measure = ( ) => setHasClippedCells ( tableHasClippedCells ( container , table ) ) ;
61- measure ( ) ;
62- // A streamed cell can grow until it clips while the table keeps its size, and a
63- // narrower column can clip a cell without this component rendering.
64- const mutations = new MutationObserver ( measure ) ;
65- mutations . observe ( table , { childList : true , characterData : true , subtree : true } ) ;
66- const stopResize = observeResize ( table , measure ) ;
67- return ( ) => {
68- mutations . disconnect ( ) ;
69- stopResize ( ) ;
70- containerRef . current = null ;
71- } ;
72- } ;
73-
7428 const handleCopy = ( format : "markdown" | "csv" ) => {
7529 const table = containerRef . current ?. querySelector ( "table" ) ;
7630 if ( ! table ) return ;
@@ -84,7 +38,7 @@ export function MarkdownTable({ children, ...props }: ComponentProps<"table">) {
8438
8539 return (
8640 < div
87- ref = { attachContainer }
41+ ref = { containerRef }
8842 className = "chat-markdown-table-container"
8943 data-expanded = { expanded ? "true" : "false" }
9044 >
@@ -93,27 +47,23 @@ export function MarkdownTable({ children, ...props }: ComponentProps<"table">) {
9347 </ ScrollArea >
9448 { /* Stays within the row's px-1: the chat lane clips sideways overflow. */ }
9549 < div className = "mt-0.5 -mx-1 flex items-center justify-between select-none" >
96- { hasClippedCells ? (
97- < Tooltip >
98- < TooltipTrigger
99- render = {
100- < Button
101- type = "button"
102- variant = { expanded ? "secondary" : "ghost-muted" }
103- size = "icon-xs"
104- aria-pressed = { expanded }
105- onClick = { ( ) => setExpanded ( ( value ) => ! value ) }
106- aria-label = { expandLabel }
107- />
108- }
109- >
110- < MorphIcon className = "size-3" icon = { expanded ? Minimize2 : Maximize2 } />
111- </ TooltipTrigger >
112- < TooltipPopup side = "top" > { expandLabel } </ TooltipPopup >
113- </ Tooltip >
114- ) : (
115- < span />
116- ) }
50+ < Tooltip >
51+ < TooltipTrigger
52+ render = {
53+ < Button
54+ type = "button"
55+ variant = { expanded ? "secondary" : "ghost-muted" }
56+ size = "icon-xs"
57+ aria-pressed = { expanded }
58+ onClick = { ( ) => setExpanded ( ( value ) => ! value ) }
59+ aria-label = { expandLabel }
60+ />
61+ }
62+ >
63+ < MorphIcon className = "size-3" icon = { expanded ? Minimize2 : Maximize2 } />
64+ </ TooltipTrigger >
65+ < TooltipPopup side = "top" > { expandLabel } </ TooltipPopup >
66+ </ Tooltip >
11767 < Menu >
11868 < Tooltip >
11969 < TooltipTrigger
0 commit comments