2828 SelectFromTable ,
2929)
3030from dt_browser .bookmarks import Bookmarks
31+ from dt_browser .column_metadata import ColumnMetadata
3132from dt_browser .column_selector import ColumnSelector
3233from dt_browser .custom_table import CustomTable , _color_name , polars_list_to_string
3334from dt_browser .expression_box import ExpressionBox
@@ -248,9 +249,8 @@ def compute_active_search_idx_display(self):
248249class RowDetail (Widget , can_focus = False , can_focus_children = False ):
249250 DEFAULT_CSS = """
250251RowDetail {
251- width: auto;
252- max_width: 50%;
253- min_width: 30%;
252+ width: 100%;
253+ height: 1fr;
254254 padding: 0 1;
255255 border: tall $primary;
256256}
@@ -288,14 +288,43 @@ def watch_row_df(self):
288288 assert self ._schema is not None
289289 display_df = display_df .join (self ._schema , on = ["Field" ]).select (["Field" , "dtype" , "Value" ])
290290 self ._dt .set_dt (display_df , display_df .with_row_index (name = INDEX_COL ).select ([INDEX_COL ]))
291- self .styles .width = self ._dt .virtual_size .width + self .gutter .width + 1
292291 self ._dt .refresh ()
292+ if isinstance (self .parent , DetailPanel ):
293+ self .parent .update_width ()
293294 # self._dt.go_to_cell(coord)
294295
296+ @property
297+ def content_width (self ) -> int :
298+ return self ._dt .virtual_size .width + self .gutter .width + 1
299+
295300 def compose (self ):
296301 yield self ._dt
297302
298303
304+ class DetailPanel (Widget , can_focus = False ):
305+ DEFAULT_CSS = """
306+ DetailPanel {
307+ max-width: 50%;
308+ min-width: 30%;
309+ layout: vertical;
310+ }
311+ """
312+
313+ def __init__ (self , row_detail : RowDetail , column_metadata : ColumnMetadata , * args , ** kwargs ):
314+ super ().__init__ (* args , ** kwargs )
315+ self ._row_detail = row_detail
316+ self ._column_metadata = column_metadata
317+
318+ def update_width (self ) -> None :
319+ row_detail_width = self ._row_detail .content_width if not self ._row_detail .row_df .is_empty () else 0
320+ meta_width = self ._column_metadata .content_size .width + self ._column_metadata .gutter .width
321+ self .styles .width = max (row_detail_width , meta_width )
322+
323+ def compose (self ):
324+ yield self ._row_detail
325+ yield self ._column_metadata
326+
327+
299328def from_file_path (path : pathlib .Path , has_header : bool = True ) -> pl .DataFrame :
300329
301330 if path .suffix in [".arrow" , ".feather" ]:
@@ -343,6 +372,7 @@ class DtBrowser(Widget): # pylint: disable=too-many-public-methods,too-many-ins
343372 current_filter = reactive [str | None ](None )
344373
345374 cur_row = reactive (0 )
375+ cur_col = reactive (0 )
346376 cur_total_rows = reactive (0 )
347377 total_rows = reactive (0 )
348378
@@ -401,6 +431,9 @@ def __init__(
401431 self ._ts_col_selector .styles .width = 1
402432
403433 self ._row_detail = RowDetail ()
434+ self ._column_metadata = ColumnMetadata ()
435+ self ._column_metadata .set_source_df (self ._filtered_dt )
436+ self ._detail_panel = DetailPanel (self ._row_detail , self ._column_metadata )
404437
405438 self ._color_by_cache : LRUCache [tuple [str , ...], pl .Series ] = LRUCache (5 )
406439 self ._last_message_ts = time .time ()
@@ -629,10 +662,10 @@ async def action_show_save(self):
629662
630663 async def watch_show_row_detail (self ):
631664 if not self .show_row_detail :
632- if existing := self .query (RowDetail ):
665+ if existing := self .query (DetailPanel ):
633666 existing .remove ()
634667 elif not self ._display_dt .is_empty ():
635- await self .query_one ("#main_hori" , Horizontal ).mount (self ._row_detail )
668+ await self .query_one ("#main_hori" , Horizontal ).mount (self ._detail_panel )
636669
637670 async def action_show_bookmarks (self ):
638671 await self .mount (self ._bookmarks , before = self .query_one (TableFooter ))
@@ -650,6 +683,8 @@ async def action_timestamp_selector(self):
650683 def _set_filtered_dt (self , filtered_dt : pl .DataFrame , filtered_meta : pl .DataFrame , ** kwargs ):
651684 self ._filtered_dt = filtered_dt
652685 self ._meta_dt = filtered_meta
686+ self ._column_metadata .set_source_df (self ._filtered_dt )
687+ self ._column_metadata .invalidate_cache ()
653688 self ._set_active_dt (self ._filtered_dt , ** kwargs )
654689
655690 def _set_active_dt (self , active_dt : pl .DataFrame , new_row : int | None = None ):
@@ -720,10 +755,20 @@ def enable_select_from_table(self, event: SelectFromTable):
720755 @on (CustomTable .CellHighlighted , selector = "#main_table" )
721756 async def handle_cell_highlight (self , event : CustomTable .CellHighlighted ):
722757 self .cur_row = event .coordinate .row
758+ col = event .coordinate .column
759+ if col != self .cur_col :
760+ self .cur_col = col
723761
724762 def watch_cur_row (self ):
725763 self ._row_detail .row_df = self ._display_dt [self .cur_row ]
726764
765+ def watch_cur_col (self ):
766+ if self ._display_dt .is_empty () or self .cur_col >= len (self ._display_dt .columns ):
767+ return
768+ col_name = self ._display_dt .columns [self .cur_col ]
769+ dtype = self ._display_dt .schema [col_name ]
770+ self ._column_metadata .column_info = (col_name , dtype )
771+
727772 @on (CustomTable .CellSelected , selector = "#main_table" )
728773 def handle_cell_select (self , event : CustomTable .CellSelected ):
729774 if self ._select_interest :
@@ -863,6 +908,9 @@ def on_mount(self):
863908 self .cur_total_rows = len (self ._display_dt )
864909 self .total_rows = len (self ._original_dt )
865910 self ._row_detail .row_df = self ._display_dt [0 ]
911+ if not self ._display_dt .is_empty ():
912+ col_name = self ._display_dt .columns [0 ]
913+ self ._column_metadata .column_info = (col_name , self ._display_dt .schema [col_name ])
866914 if self .removed_cols :
867915 err_str = ", " .join (f"{ k } : { v } " for k , v in self .removed_cols .items ())
868916 self .notify (
0 commit comments