nebius/SWE-rebench-V2-PRs huggingface dataset:
Repository: dcs4cop/xcube
Pull request #357 (2020-11-05 14:21:58)

Minor changes to allow generating xcube datasets from BC Sentinel-2 data
 `xcube gen` can now interpret `start_date` and `stop_date` from a netcdfs attributes. This is relevant for using
`xcube gen` for Sentinel-2 data processed and provided by Brockmann Consult. 


Patch:
diff --git a/CHANGES.md b/CHANGES.md
index 75d6da1e..060aca5e 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,6 @@
 ## Changes in 0.5.2 (in development)
-
+* `xcube gen` can now interpret `start_date` and `stop_date` from a netcdfs attributes. This is relevant for using
+  `xcube gen` for Sentinel-2 Level 2 data products generated and provided by Brockmann Consult. 
 * For `xcube serve` dataset configurations where `FileSystem: obs`, users must now also 
   specify `Anonymous: True` for datasets in public object storage buckets. For example:
 ```yaml
diff --git a/xcube/core/gen/iproc.py b/xcube/core/gen/iproc.py
index b3f0b7d1..432c9434 100644
--- a/xcube/core/gen/iproc.py
+++ b/xcube/core/gen/iproc.py
@@ -438,6 +438,9 @@ class DefaultInputProcessor(XYInputProcessor):
         elif "start_time" in dataset.attrs:
             time_start = str(dataset.attrs["start_time"])
             time_stop = str(dataset.attrs.get("stop_time", dataset.attrs.get("end_time", time_start)))
+        elif "start_date" in dataset.attrs:
+            time_start = str(dataset.attrs["start_date"])
+            time_stop = str(dataset.attrs.get("stop_date", dataset.attrs.get("end_time", time_start)))
         return time_start, time_stop
 
     def _validate(self, dataset):


Test patch:
diff --git a/test/core/gen/test_iproc.py b/test/core/gen/test_iproc.py
index 483890a1..6e5beef4 100644
--- a/test/core/gen/test_iproc.py
+++ b/test/core/gen/test_iproc.py
@@ -84,6 +84,10 @@ class DefaultInputProcessorTest(unittest.TestCase):
         t1, t2 = self.processor.get_time_range(ds)
         self.assertEqual(to_time_in_days_since_1970("20100301T000000Z"), t1)
         self.assertEqual(to_time_in_days_since_1970("20100301T235959Z"), t2)
+        ds = create_default_dataset(time_mode="start_stop_date")
+        t1, t2 = self.processor.get_time_range(ds)
+        self.assertEqual(to_time_in_days_since_1970("20100301T000000Z"), t1)
+        self.assertEqual(to_time_in_days_since_1970("20100301T235959Z"), t2)
         ds = create_default_dataset(time_mode="no_time")
         with self.assertRaises(ValueError) as cm:
             self.processor.get_time_range(ds)
@@ -180,4 +184,10 @@ def create_default_dataset(time_mode: str = "time_bnds"):
             ('stop_time', '20100301T235959Z'),
         ]))
 
+    elif time_mode == "start_stop_date":
+        attrs.update(dict([
+            ('start_date', '01-MAR-2010 00:00:00.000000'),
+            ('stop_date', '01-MAR-2010 23:59:59.000000'),
+        ]))
+
     return xr.Dataset(coords=coords, data_vars=data_vars, attrs=attrs)
Repository: Evertras/bubble-table
Pull request #23 (2022-02-22 14:55:42)

Strlimit sane behavior when max len is 0


Patch:
diff --git a/table/strlimit.go b/table/strlimit.go
index 7a25f85..3e1a02b 100644
--- a/table/strlimit.go
+++ b/table/strlimit.go
@@ -1,9 +1,13 @@
 package table
 
-func limitStr(s string, maxLen int) string {
-	if len(s) > maxLen {
-		return s[:maxLen-1] + "…"
+func limitStr(str string, maxLen int) string {
+	if maxLen == 0 {
+		return ""
 	}
 
-	return s
+	if len(str) > maxLen {
+		return str[:maxLen-1] + "…"
+	}
+
+	return str
 }


Test patch:
diff --git a/table/strlimit_test.go b/table/strlimit_test.go
index 018b5d8..626a93f 100644
--- a/table/strlimit_test.go
+++ b/table/strlimit_test.go
@@ -38,6 +38,12 @@ func TestLimitStr(t *testing.T) {
 			max:      8,
 			expected: "Hello t…",
 		},
+		{
+			name:     "Zero max",
+			input:    "Hello",
+			max:      0,
+			expected: "",
+		},
 	}
 
 	for _, test := range tests {
Repository: Evertras/bubble-table
Pull request #33 (2022-02-26 13:03:15)

Fix selection
Selection was broken by the sort functionality.  This is a quick fix since #29 will adjust how sorted rows are stored in the back-end.


Patch:
diff --git a/table/update.go b/table/update.go
index e36d854..2127c03 100644
--- a/table/update.go
+++ b/table/update.go
@@ -30,16 +30,16 @@ func (m *Model) toggleSelect() {
 		return
 	}
 
-	rows := make([]Row, len(m.rows))
-	copy(rows, m.rows)
+	rows := make([]Row, len(m.sortedRows))
+	copy(rows, m.sortedRows)
 
 	rows[m.rowCursorIndex].selected = !rows[m.rowCursorIndex].selected
 
-	m.rows = rows
+	m.sortedRows = rows
 
 	m.selectedRows = []Row{}
 
-	for _, row := range m.rows {
+	for _, row := range m.sortedRows {
 		if row.selected {
 			m.selectedRows = append(m.selectedRows, row)
 		}


Test patch:
diff --git a/table/update_test.go b/table/update_test.go
index 6e2c366..605e529 100644
--- a/table/update_test.go
+++ b/table/update_test.go
@@ -192,15 +192,15 @@ func TestSelectingRowToggles(t *testing.T) {
 	keyEnter := tea.KeyMsg{Type: tea.KeyEnter}
 	keyDown := tea.KeyMsg{Type: tea.KeyDown}
 
-	assert.False(t, model.rows[0].selected, "Row shouldn't be selected to start")
+	assert.False(t, model.sortedRows[0].selected, "Row shouldn't be selected to start")
 
 	model, _ = model.Update(keyEnter)
-	assert.True(t, model.rows[0].selected, "Row should be selected after first toggle")
+	assert.True(t, model.sortedRows[0].selected, "Row should be selected after first toggle")
 
 	model, _ = model.Update(keyEnter)
-	assert.False(t, model.rows[0].selected, "Row should not be selected after second toggle")
+	assert.False(t, model.sortedRows[0].selected, "Row should not be selected after second toggle")
 
 	model, _ = model.Update(keyDown)
 	model, _ = model.Update(keyEnter)
-	assert.True(t, model.rows[1].selected, "Second row should be selected after moving and toggling")
+	assert.True(t, model.sortedRows[1].selected, "Second row should be selected after moving and toggling")
 }
Repository: Evertras/bubble-table
Pull request #36 (2022-03-04 13:00:23)

Fix footer style for static footers


Patch:
diff --git a/table/footer.go b/table/footer.go
index cebade3..ad77f8b 100644
--- a/table/footer.go
+++ b/table/footer.go
@@ -14,8 +14,10 @@ func (m Model) renderFooter() string {
 		return ""
 	}
 
+	styleFooter := m.baseStyle.Copy().Inherit(m.border.styleFooter).Width(m.totalWidth)
+
 	if m.staticFooter != "" {
-		return m.border.styleFooter.Width(m.totalWidth).Render(m.staticFooter)
+		return styleFooter.Render(m.staticFooter)
 	}
 
 	sections := []string{}
@@ -31,7 +33,5 @@ func (m Model) renderFooter() string {
 
 	footerText := strings.Join(sections, " ")
 
-	styleFooter := m.baseStyle.Copy().Inherit(m.border.styleFooter).Width(m.totalWidth)
-
 	return styleFooter.Render(footerText)
 }


Test patch:
diff --git a/table/view_test.go b/table/view_test.go
index a34bceb..8f70f2b 100644
--- a/table/view_test.go
+++ b/table/view_test.go
@@ -207,6 +207,24 @@ func TestSingleRowWithFooterView(t *testing.T) {
 	assert.Equal(t, expectedTable, rendered)
 }
 
+func TestSingleRowWithFooterViewAndBaseStyle(t *testing.T) {
+	model := New([]Column{
+		NewColumn("1", "1", 4),
+		NewColumn("2", "2", 4),
+		NewColumn("3", "3", 4),
+	}).WithStaticFooter("Footer").WithBaseStyle(lipgloss.NewStyle().Align(lipgloss.Left))
+
+	const expectedTable = `┏━━━━┳━━━━┳━━━━┓
+┃1   ┃2   ┃3   ┃
+┣━━━━┻━━━━┻━━━━┫
+┃Footer        ┃
+┗━━━━━━━━━━━━━━┛`
+
+	rendered := model.View()
+
+	assert.Equal(t, expectedTable, rendered)
+}
+
 func TestSingleColumnWithFooterView(t *testing.T) {
 	model := New([]Column{
 		NewColumn("id", "ID", 4),
Repository: Evertras/bubble-table
Pull request #43 (2022-03-07 16:37:35)

Add function to set the highlighted row - fixes issue #42
Allow setting which row will be highlighted by passing its index

gairadzi: Sure, sounds good.
Do you prefer me to move it to the `options.go` file?
Evertras: Yep that LGTM, just need to fix the lint issue (running `make fmt` should do it) and a quick test for coverage would be great.
Evertras: (Quick link to #42 since the title doesn't trigger Github linking the issue/PR)
Evertras: LGTM, the tiny dip in coverage is fine.  I'll add a test or two later.  Thanks for the contribution!

Patch:
diff --git a/table/options.go b/table/options.go
index 3b56e80..a8046c4 100644
--- a/table/options.go
+++ b/table/options.go
@@ -4,6 +4,23 @@ import (
 	"github.com/charmbracelet/lipgloss"
 )
 
+// WithHighlightedRow sets the highlighted row to the given index.
+func (m Model) WithHighlightedRow(index int) Model {
+	m.rowCursorIndex = index
+
+	if m.rowCursorIndex < 0 {
+		m.rowCursorIndex = 0
+	}
+
+	if m.rowCursorIndex >= len(m.GetVisibleRows()) {
+		m.rowCursorIndex = len(m.GetVisibleRows()) - 1
+	}
+
+	m.currentPage = m.expectedPageForRowIndex(m.rowCursorIndex)
+
+	return m
+}
+
 // HeaderStyle sets the style to apply to the header text, such as color or bold.
 func (m Model) HeaderStyle(style lipgloss.Style) Model {
 	m.headerStyle = style.Copy()


Test patch:
diff --git a/table/options_test.go b/table/options_test.go
new file mode 100644
index 0000000..39bf348
--- /dev/null
+++ b/table/options_test.go
@@ -0,0 +1,26 @@
+package table
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestWithHighlightedRowSet(t *testing.T) {
+	highlightedIndex := 1
+
+	cols := []Column{
+		NewColumn("id", "ID", 3),
+	}
+
+	model := New(cols).WithRows([]Row{
+		NewRow(RowData{
+			"id": "first",
+		}),
+		NewRow(RowData{
+			"id": "second",
+		}),
+	}).WithHighlightedRow(highlightedIndex)
+
+	assert.Equal(t, model.rows[highlightedIndex], model.HighlightedRow())
+}
Repository: Evertras/bubble-table
Pull request #52 (2022-03-13 11:54:18)

Fix table layout for multi-line values - fixes issue #51
If one of the rows has a multi-line value we want
to truncate it and display one the first line

Evertras: This seems like reasonable behavior to me.  Thanks for the contribution!

Patch:
diff --git a/table/strlimit.go b/table/strlimit.go
index 8cd4be3..6428b7d 100644
--- a/table/strlimit.go
+++ b/table/strlimit.go
@@ -1,14 +1,28 @@
 package table
 
-import "github.com/mattn/go-runewidth"
+import (
+	"strings"
+
+	"github.com/mattn/go-runewidth"
+)
 
 func limitStr(str string, maxLen int) string {
 	if maxLen == 0 {
 		return ""
 	}
+
+	newLineIndex := strings.Index(str, "\n")
+	if newLineIndex > -1 {
+		str = str[:newLineIndex]
+	}
+
 	if runewidth.StringWidth(str) > maxLen {
 		return runewidth.Truncate(str, maxLen-1, "") + "…"
 	}
 
+	if newLineIndex > -1 {
+		return str + "…"
+	}
+
 	return str
 }


Test patch:
diff --git a/table/strlimit_test.go b/table/strlimit_test.go
index 0ed5730..2723eff 100644
--- a/table/strlimit_test.go
+++ b/table/strlimit_test.go
@@ -70,6 +70,12 @@ func TestLimitStr(t *testing.T) {
 			max:      5,
 			expected: "直立…",
 		},
+		{
+			name:     "multiline truncated",
+			input:    "hi\nall",
+			max:      5,
+			expected: "hi…",
+		},
 	}
 
 	for _, test := range tests {
Repository: Evertras/bubble-table
Pull request #53 (2022-03-13 12:48:56)

Fix edge case for multiline truncation on exact max length
Fixes the edge case remaining for #51 where in a multiline string, if the first line was exactly the max width, the extra added character would break the format.

Evertras: Missing out on `0.01%` of coverage is fine here because it's just reducing the number of lines of code total.

Patch:
diff --git a/table/strlimit.go b/table/strlimit.go
index 6428b7d..e21d97e 100644
--- a/table/strlimit.go
+++ b/table/strlimit.go
@@ -13,16 +13,12 @@ func limitStr(str string, maxLen int) string {
 
 	newLineIndex := strings.Index(str, "\n")
 	if newLineIndex > -1 {
-		str = str[:newLineIndex]
+		str = str[:newLineIndex] + "…"
 	}
 
 	if runewidth.StringWidth(str) > maxLen {
 		return runewidth.Truncate(str, maxLen-1, "") + "…"
 	}
 
-	if newLineIndex > -1 {
-		return str + "…"
-	}
-
 	return str
 }


Test patch:
diff --git a/table/strlimit_test.go b/table/strlimit_test.go
index 2723eff..d9b652a 100644
--- a/table/strlimit_test.go
+++ b/table/strlimit_test.go
@@ -71,11 +71,17 @@ func TestLimitStr(t *testing.T) {
 			expected: "直立…",
 		},
 		{
-			name:     "multiline truncated",
+			name:     "Multiline truncated",
 			input:    "hi\nall",
 			max:      5,
 			expected: "hi…",
 		},
+		{
+			name:     "Multiline with exact max width",
+			input:    "hello\nall",
+			max:      5,
+			expected: "hell…",
+		},
 	}
 
 	for _, test := range tests {
Repository: Evertras/bubble-table
Pull request #88 (2022-06-04 14:17:23)

Add programmatic access to scroll left and right
Related to #83 , just add some quick accessors to be able to programmatically scroll left/right.


Patch:
diff --git a/table/options.go b/table/options.go
index 9170d98..9b3cf9d 100644
--- a/table/options.go
+++ b/table/options.go
@@ -286,3 +286,17 @@ func (m Model) WithHorizontalFreezeColumnCount(columnsToFreeze int) Model {
 
 	return m
 }
+
+// ScrollRight moves one column to the right.  Use with WithMaxTotalWidth.
+func (m Model) ScrollRight() Model {
+	m.scrollRight()
+
+	return m
+}
+
+// ScrollLeft moves one column to the left.  Use with WithMaxTotalWidth.
+func (m Model) ScrollLeft() Model {
+	m.scrollLeft()
+
+	return m
+}


Test patch:
diff --git a/table/update_test.go b/table/update_test.go
index 19b1b3d..af5688e 100644
--- a/table/update_test.go
+++ b/table/update_test.go
@@ -413,11 +413,12 @@ func TestScrollRightWithFooter(t *testing.T) {
 ┗━━━━━━━━━━━━━━━━┛`
 
 	hitScrollRight := func() {
-		model, _ = model.Update(tea.KeyMsg{Type: tea.KeyShiftRight})
+		// Try the programmatic API
+		model = model.ScrollRight()
 	}
 
 	hitScrollLeft := func() {
-		model, _ = model.Update(tea.KeyMsg{Type: tea.KeyShiftLeft})
+		model = model.ScrollLeft()
 	}
 
 	assert.Equal(t, expectedTableOriginal, model.View())
Repository: Evertras/bubble-table
Pull request #92 (2022-06-05 02:36:01)

Right-align caret
Helps #83 

Always right-align the right scroll caret for consistency.


Patch:
diff --git a/table/row.go b/table/row.go
index 3a3fb6b..b7f1ee5 100644
--- a/table/row.go
+++ b/table/row.go
@@ -56,6 +56,7 @@ func (m Model) renderRowColumnData(row Row, column Column, rowStyle lipgloss.Sty
 			str = m.unselectedText
 		}
 	} else if column.key == columnKeyOverflowRight {
+		cellStyle = cellStyle.Align(lipgloss.Right)
 		str = ">"
 	} else if column.key == columnKeyOverflowLeft {
 		str = "<"


Test patch:
diff --git a/table/view_test.go b/table/view_test.go
index d969b63..8169737 100644
--- a/table/view_test.go
+++ b/table/view_test.go
@@ -812,3 +812,36 @@ func TestScrolledTableSizesFooterCorrectly(t *testing.T) {
 	rendered := model.View()
 	assert.Equal(t, expectedTable, rendered)
 }
+
+func TestHorizontalScrollCaretIsRightAligned(t *testing.T) {
+	leftAlign := lipgloss.NewStyle().Align(lipgloss.Left)
+	model := New([]Column{
+		NewColumn("1", "1", 4),
+		NewColumn("2", "2", 4),
+		NewColumn("3", "3", 4),
+		NewColumn("4", "4", 4),
+	}).
+		WithRows([]Row{
+			NewRow(RowData{
+				"1": "x1",
+				"2": "x2",
+				"3": "x3",
+				"4": "x4",
+			}).WithStyle(leftAlign),
+		}).
+		HeaderStyle(leftAlign).
+		WithStaticFooter("Footer").
+		WithMaxTotalWidth(17)
+
+	const expectedTable = `┏━━━━┳━━━━┳━━━━━┓
+┃1   ┃2   ┃    >┃
+┣━━━━╋━━━━╋━━━━━┫
+┃x1  ┃x2  ┃    >┃
+┣━━━━┻━━━━┻━━━━━┫
+┃         Footer┃
+┗━━━━━━━━━━━━━━━┛`
+
+	rendered := model.View()
+
+	assert.Equal(t, expectedTable, rendered)
+}
Repository: Evertras/bubble-table
Pull request #109 (2022-06-23 13:22:17)

Fix edge case of empty table with row index of -1
There was an edge case where the highlighted row index might be -1 for an empty table.


Patch:
diff --git a/table/options.go b/table/options.go
index 171b264..f1f2e0a 100644
--- a/table/options.go
+++ b/table/options.go
@@ -9,14 +9,14 @@ import (
 func (m Model) WithHighlightedRow(index int) Model {
 	m.rowCursorIndex = index
 
-	if m.rowCursorIndex < 0 {
-		m.rowCursorIndex = 0
-	}
-
 	if m.rowCursorIndex >= len(m.GetVisibleRows()) {
 		m.rowCursorIndex = len(m.GetVisibleRows()) - 1
 	}
 
+	if m.rowCursorIndex < 0 {
+		m.rowCursorIndex = 0
+	}
+
 	m.currentPage = m.expectedPageForRowIndex(m.rowCursorIndex)
 
 	return m
@@ -37,6 +37,10 @@ func (m Model) WithRows(rows []Row) Model {
 		m.rowCursorIndex = len(m.rows) - 1
 	}
 
+	if m.rowCursorIndex < 0 {
+		m.rowCursorIndex = 0
+	}
+
 	if m.pageSize != 0 {
 		maxPage := m.MaxPages()
 


Test patch:
diff --git a/table/events_test.go b/table/events_test.go
index e598a00..f2a33dc 100644
--- a/table/events_test.go
+++ b/table/events_test.go
@@ -91,4 +91,9 @@ func TestUserEventHighlightedIndexChanged(t *testing.T) {
 	hitDown()
 	events = model.GetLastUpdateUserEvents()
 	assert.Len(t, events, 0, "There's no row to change to for single row table, event shouldn't exist")
+
+	model = model.WithRows([]Row{})
+	hitDown()
+	events = model.GetLastUpdateUserEvents()
+	assert.Len(t, events, 0, "There's no row to change to for an empty table, event shouldn't exist")
 }
Repository: Evertras/bubble-table
Pull request #117 (2022-07-04 12:33:28)

Allow setting filter value directly with string
Exposes the `SetValue()` function of the filter text input so that filters can be set directly with strings.

Helps #116 


Patch:
diff --git a/table/options.go b/table/options.go
index 6b08c8c..ca51c96 100644
--- a/table/options.go
+++ b/table/options.go
@@ -284,6 +284,15 @@ func (m Model) WithFilterInput(input textinput.Model) Model {
 	return m
 }
 
+// WithFilterInputValue sets the filter value to the given string, immediately
+// applying it as if the user had typed it in.  Useful for external filter inputs
+// that are not necessarily a text input.
+func (m Model) WithFilterInputValue(value string) Model {
+	m.filterTextInput.SetValue(value)
+
+	return m
+}
+
 // WithFooterVisibility sets the visibility of the footer.
 func (m Model) WithFooterVisibility(visibility bool) Model {
 	m.footerVisible = visibility


Test patch:
diff --git a/table/filter_test.go b/table/filter_test.go
index 3eb987b..3c10c88 100644
--- a/table/filter_test.go
+++ b/table/filter_test.go
@@ -204,3 +204,34 @@ func TestFilterWithExternalTextInput(t *testing.T) {
 
 	assert.Len(t, filteredRows, 1)
 }
+
+func TestFilterWithSetValue(t *testing.T) {
+	columns := []Column{NewColumn("title", "title", 10).WithFiltered(true)}
+	rows := []Row{
+		NewRow(RowData{
+			"title":       "AAA",
+			"description": "",
+		}),
+		NewRow(RowData{
+			"title":       "BBB",
+			"description": "",
+		}),
+		// Empty
+		NewRow(RowData{}),
+	}
+	model := New(columns).WithRows(rows).Filtered(true)
+	model = model.WithFilterInputValue("AaA")
+
+	filteredRows := model.getFilteredRows(rows)
+	assert.Len(t, filteredRows, 1)
+
+	// Make sure it holds true after an update
+	model, _ = model.Update(tea.KeyRight)
+	filteredRows = model.getFilteredRows(rows)
+	assert.Len(t, filteredRows, 1)
+
+	// Remove filter
+	model = model.WithFilterInputValue("")
+	filteredRows = model.getFilteredRows(rows)
+	assert.Len(t, filteredRows, 3)
+}
Repository: Evertras/bubble-table
Pull request #123 (2022-07-20 12:12:08)

Add query for if filter input is currently focused
Adds query for if the filter input is currently focused, indicating that the user is actively typing into the filter field.  Useful for other controls to know whether they should react to typing or not.

Helps #122 


Patch:
diff --git a/table/query.go b/table/query.go
index 16b08fd..c49556c 100644
--- a/table/query.go
+++ b/table/query.go
@@ -25,6 +25,12 @@ func (m *Model) GetIsFilterActive() bool {
 	return m.filterTextInput.Value() != ""
 }
 
+// GetIsFilterInputFocused returns true if the table's built-in filter input is
+// currently focused.
+func (m *Model) GetIsFilterInputFocused() bool {
+	return m.filterTextInput.Focused()
+}
+
 // GetCurrentFilter returns the current filter text being applied, or an empty
 // string if none is applied.
 func (m *Model) GetCurrentFilter() string {


Test patch:
diff --git a/table/query_test.go b/table/query_test.go
index 1115073..123948b 100644
--- a/table/query_test.go
+++ b/table/query_test.go
@@ -180,3 +180,22 @@ func TestGetPaginationWrapping(t *testing.T) {
 
 	assert.False(t, model.GetPaginationWrapping(), "Pagination wrapping setting did not update after setting option")
 }
+
+func TestGetIsFilterInputFocused(t *testing.T) {
+	model := New([]Column{}).Filtered(true).Focused(true)
+
+	assert.False(t, model.GetIsFilterInputFocused(), "Text input shouldn't start focused")
+
+	model, _ = model.Update(tea.KeyMsg{
+		Type:  tea.KeyRunes,
+		Runes: []rune{'/'},
+	})
+
+	assert.True(t, model.GetIsFilterInputFocused(), "Did not trigger text input")
+
+	model, _ = model.Update(tea.KeyMsg{
+		Type: tea.KeyEnter,
+	})
+
+	assert.False(t, model.GetIsFilterInputFocused(), "Should no longer be focused after hitting enter")
+}
Repository: Evertras/bubble-table
Pull request #124 (2022-07-20 12:22:03)

Add events for filter input being focused/unfocused
Adds events for when the built-in filter text box is focused or unfocused, allowing the outer app to react to it if needed.

Helps #122 


Patch:
diff --git a/table/events.go b/table/events.go
index 1a3d823..4b5e2b0 100644
--- a/table/events.go
+++ b/table/events.go
@@ -48,3 +48,13 @@ type UserEventRowSelectToggled struct {
 	RowIndex   int
 	IsSelected bool
 }
+
+// UserEventFilterInputFocused indicates that the user has focused the filter
+// text input, so that any other typing will type into the filter field.  Only
+// activates for the built-in filter text box.
+type UserEventFilterInputFocused struct{}
+
+// UserEventFilterInputUnfocused indicates that the user has unfocused the filter
+// text input, which means the user is done typing into the filter field.  Only
+// activates for the built-in filter text box.
+type UserEventFilterInputUnfocused struct{}
diff --git a/table/update.go b/table/update.go
index 071cfe6..985a7a5 100644
--- a/table/update.go
+++ b/table/update.go
@@ -93,6 +93,7 @@ func (m *Model) handleKeypress(msg tea.KeyMsg) {
 
 	if key.Matches(msg, m.keyMap.Filter) {
 		m.filterTextInput.Focus()
+		m.appendUserEvent(UserEventFilterInputFocused{})
 	}
 
 	if key.Matches(msg, m.keyMap.FilterClear) {
@@ -124,7 +125,14 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
 	}
 
 	if m.filterTextInput.Focused() {
-		return m.updateFilterTextInput(msg)
+		var cmd tea.Cmd
+		m, cmd = m.updateFilterTextInput(msg)
+
+		if !m.filterTextInput.Focused() {
+			m.appendUserEvent(UserEventFilterInputUnfocused{})
+		}
+
+		return m, cmd
 	}
 
 	switch msg := msg.(type) {


Test patch:
diff --git a/table/events_test.go b/table/events_test.go
index 389c031..4ddb62d 100644
--- a/table/events_test.go
+++ b/table/events_test.go
@@ -172,3 +172,36 @@ func TestUserEventRowSelectToggled(t *testing.T) {
 	events = model.GetLastUpdateUserEvents()
 	assert.Len(t, events, 0, "There's no row to select for an empty table, event shouldn't exist")
 }
+
+func TestFilterFocusEvents(t *testing.T) {
+	model := New([]Column{}).Filtered(true).Focused(true)
+
+	events := model.GetLastUpdateUserEvents()
+
+	assert.Empty(t, events, "Unexpected events to start")
+
+	// Start filter
+	model, _ = model.Update(tea.KeyMsg{
+		Type:  tea.KeyRunes,
+		Runes: []rune{'/'},
+	})
+	events = model.GetLastUpdateUserEvents()
+	assert.Len(t, events, 1, "Only expected one event")
+	switch events[0].(type) {
+	case UserEventFilterInputFocused:
+	default:
+		assert.FailNow(t, "Unexpected event type")
+	}
+
+	// Stop filter
+	model, _ = model.Update(tea.KeyMsg{
+		Type: tea.KeyEnter,
+	})
+	events = model.GetLastUpdateUserEvents()
+	assert.Len(t, events, 1, "Only expected one event")
+	switch events[0].(type) {
+	case UserEventFilterInputUnfocused:
+	default:
+		assert.FailNow(t, "Unexpected event type")
+	}
+}