Repository: Evertras/bubble-table
Pull request #49 (2022-03-08 17:18:03)

Fix flex footer
A static footer wasn't rendering properly with flex columns due to the width being calculated incorrectly and at the wrong time.  This makes things a little clearer in terms of what `totalWidth` actually means (it's the full visible width of the table) and makes it work more nicely with the target flex width.

Evertras: Should fix #48 

Patch:
diff --git a/examples/flex/main.go b/examples/flex/main.go
index 63bdc54..a4b9030 100644
--- a/examples/flex/main.go
+++ b/examples/flex/main.go
@@ -40,7 +40,7 @@ func NewModel() Model {
 				columnKeyElement:     "Fire",
 				columnKeyDescription: "直立した恐竜のような身体と、尻尾の先端に常に燃えている炎が特徴。",
 			}),
-		}),
+		}).WithStaticFooter("A footer!"),
 	}
 }
 
@@ -92,7 +92,7 @@ func (m *Model) recalculateTable() {
 func (m Model) View() string {
 	strs := []string{
 		"A flexible table that fills available space (Name is fixed-width)",
-		fmt.Sprintf("Total margin: %d (left/right to adjust)", m.totalMargin),
+		fmt.Sprintf("Target size: %d (left/right to adjust)", m.totalWidth-m.totalMargin),
 		"Press q or ctrl+c to quit",
 		m.flexTable.View(),
 	}
diff --git a/table/dimensions.go b/table/dimensions.go
index 2bdb5f6..c187bdc 100644
--- a/table/dimensions.go
+++ b/table/dimensions.go
@@ -1,13 +1,17 @@
 package table
 
 func (m *Model) recalculateWidth() {
-	total := 0
+	if m.targetTotalWidth != 0 {
+		m.totalWidth = m.targetTotalWidth
+	} else {
+		total := 0
 
-	for _, column := range m.columns {
-		total += column.width
-	}
+		for _, column := range m.columns {
+			total += column.width
+		}
 
-	m.totalWidth = total + len(m.columns) - 1
+		m.totalWidth = total + len(m.columns) + 1
+	}
 
 	updateColumnWidths(m.columns, m.targetTotalWidth)
 }
@@ -52,6 +56,11 @@ func updateColumnWidths(cols []Column, totalWidth int) {
 			leftoverWidth--
 		}
 
+		if index == len(cols)-1 {
+			width += leftoverWidth
+			leftoverWidth = 0
+		}
+
 		width = max(width, 1)
 
 		cols[index].width = width
diff --git a/table/footer.go b/table/footer.go
index ad77f8b..2af09ac 100644
--- a/table/footer.go
+++ b/table/footer.go
@@ -14,7 +14,9 @@ func (m Model) renderFooter() string {
 		return ""
 	}
 
-	styleFooter := m.baseStyle.Copy().Inherit(m.border.styleFooter).Width(m.totalWidth)
+	const borderAdjustment = 2
+
+	styleFooter := m.baseStyle.Copy().Inherit(m.border.styleFooter).Width(m.totalWidth - borderAdjustment)
 
 	if m.staticFooter != "" {
 		return styleFooter.Render(m.staticFooter)


Test patch:
diff --git a/table/view_test.go b/table/view_test.go
index 59211c6..ce99a0b 100644
--- a/table/view_test.go
+++ b/table/view_test.go
@@ -7,6 +7,7 @@ import (
 
 	tea "github.com/charmbracelet/bubbletea"
 	"github.com/charmbracelet/lipgloss"
+	"github.com/mattn/go-runewidth"
 	"github.com/stretchr/testify/assert"
 )
 
@@ -522,3 +523,42 @@ func TestSimpleFlex3x3(t *testing.T) {
 
 	assert.Equal(t, expectedTable, rendered)
 }
+
+func TestSimpleFlex3x3AtAllTargetWidths(t *testing.T) {
+	model := New([]Column{
+		NewColumn("1", "1", 4),
+		NewFlexColumn("2", "2", 1),
+		NewFlexColumn("3", "3", 2),
+	}).WithTargetWidth(20)
+
+	rows := []Row{}
+
+	for rowIndex := 1; rowIndex <= 3; rowIndex++ {
+		rowData := RowData{}
+
+		for columnIndex := 1; columnIndex <= 3; columnIndex++ {
+			id := fmt.Sprintf("%d", columnIndex)
+
+			rowData[id] = fmt.Sprintf("%d,%d", columnIndex, rowIndex)
+		}
+
+		rows = append(rows, NewRow(rowData))
+	}
+
+	model = model.WithRows(rows)
+
+	for targetWidth := 15; targetWidth < 100; targetWidth++ {
+		model = model.WithTargetWidth(targetWidth)
+
+		rendered := model.View()
+
+		firstLine := strings.Split(rendered, "\n")[0]
+
+		assert.Equal(t, targetWidth, model.totalWidth)
+		assert.Equal(t, targetWidth, runewidth.StringWidth(firstLine))
+
+		if t.Failed() {
+			return
+		}
+	}
+}