From cead6d93823a3fa7c91be9681593252000db6e12 Mon Sep 17 00:00:00 2001 From: hl-valdemar Date: Sun, 18 Oct 2026 18:13:56 +0200 Subject: [PATCH] fix: preserve full language rows when filtering picker --- README.md | 2 ++ .../Conjure/ApplicationActionsController.swift | 10 +++++++++- Tests/ConjureKitTests/LanguagePickerTests.swift | 17 +++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 54e6f1d..d687b1b 100644 --- a/README.md +++ b/README.md @@ -150,6 +150,8 @@ Press Command-P or click the provider selector to expand a searchable action-sty The footer shows the current language pair (initially **English → Japanese**). Click it or open **Actions** with Command-K to change the source language, change the target language, or swap them. Source and target selection use Conjure's themed popup, not a native menu. Fuzzy-search language names (for example, `jpns` matches **Japanese**), scroll the full list, and select with Up/Down, Control-J/K, and Return or by clicking a language. A checkmark marks the current language; it is selected and scrolled into view when the picker opens. Escape, Back, Command-K, or clicking outside cancels the picker without changing languages or leaving Translate. Language choices are saved locally and apply to both providers. Changing languages clears the previous result without submitting another request. The input hint and output accessibility label follow the selected pair. Escape closes an open picker first and dismisses the window only when no picker is open. Back closes the picker first, then returns from the context step to editing the original text (discarding context), then returns to the launcher and restores your search. Command-W always dismisses the entire window. +The language picker resizes to complete rows as results are filtered, showing up to six rows before scrolling. + Editing the input, changing languages or providers, saving or forgetting the key in Settings, or leaving Translate cancels the active request; cancelled results are not shown. After a request fails, submit again to retry. - **MyMemory:** No account or key required. Limits are 500 UTF-8 bytes per request and [5,000 characters per day](https://mymemory.translated.net/doc/usagelimits.php) for anonymous use. diff --git a/Sources/Conjure/ApplicationActionsController.swift b/Sources/Conjure/ApplicationActionsController.swift index b5f6cf0..c54102b 100644 --- a/Sources/Conjure/ApplicationActionsController.swift +++ b/Sources/Conjure/ApplicationActionsController.swift @@ -43,6 +43,7 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat private var currentLanguage: String? private var languageTable: NSTableView? private var languageScroll: NSScrollView? + private var languageViewportHeight: NSLayoutConstraint? var closed: (() -> Void)? var isShown: Bool { viewIfLoaded?.superview != nil && !isClosing } @@ -59,6 +60,7 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat rowTopConstraints = [] languageTable = nil languageScroll = nil + languageViewportHeight = nil entries = [ ActionEntry(title: "Open", symbol: "arrow.up.forward.app", color: SunsetPalette.text), ActionEntry(title: "Favorite", symbol: "star", color: SunsetPalette.text), @@ -155,7 +157,9 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat ]) content.layer?.borderColor = SunsetPalette.secondaryText.withAlphaComponent(0.22).cgColor if languages != nil { + height.isActive = false let table = NSTableView() + table.style = .plain table.addTableColumn(NSTableColumn(identifier: NSUserInterfaceItemIdentifier("Language"))) table.headerView = nil table.backgroundColor = .clear @@ -177,14 +181,17 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat scroll.drawsBackground = false scroll.translatesAutoresizingMaskIntoConstraints = false content.addSubview(scroll) + let viewportHeight = scroll.heightAnchor.constraint(equalToConstant: LauncherRowView.rowHeight) NSLayoutConstraint.activate([ scroll.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: 8), scroll.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -8), scroll.topAnchor.constraint(equalTo: applicationLabel.bottomAnchor, constant: 10), scroll.bottomAnchor.constraint(equalTo: search.topAnchor, constant: -8), + viewportHeight, ]) languageTable = table languageScroll = scroll + languageViewportHeight = viewportHeight } updateSelection() } @@ -345,9 +352,10 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat let selection = (query.isEmpty ? matchingLanguages.firstIndex(where: { $0.code == currentLanguage }) : nil) ?? (matchingLanguages.isEmpty ? nil : 0) for entry in entries { entry.view.isHidden = true } - heightConstraint?.constant = 116 + CGFloat(max(min(matchingLanguages.count, 6), 1) - 1) * LauncherRowView.rowHeight + languageViewportHeight?.constant = CGFloat(max(min(matchingLanguages.count, 6), 1)) * table.rowHeight empty.isHidden = !matchingLanguages.isEmpty languageScroll?.isHidden = matchingLanguages.isEmpty + view.superview?.layoutSubtreeIfNeeded() table.reloadData() selectedEntry = selection if let selectedEntry { diff --git a/Tests/ConjureKitTests/LanguagePickerTests.swift b/Tests/ConjureKitTests/LanguagePickerTests.swift index 6c2839b..c7bcf47 100644 --- a/Tests/ConjureKitTests/LanguagePickerTests.swift +++ b/Tests/ConjureKitTests/LanguagePickerTests.swift @@ -100,5 +100,22 @@ struct LanguagePickerTests { #expect(selected == "5") popup.close(animated: false) #expect(!pill.isAccessibilityHidden()) + + popup.showLanguages(choices, selected: "15", source: true, relativeTo: anchor) + let search = try #require(descendants(popup.view).compactMap { $0 as? NSSearchField }.first) + let filteredTable = try #require(descendants(popup.view).compactMap { $0 as? NSTableView }.first) + // Shrink from a scrolled list to one result, then grow and shrink again. + for query in ["Language 19", "", "no matching language", "Language 03"] { + search.stringValue = query + search.delegate?.controlTextDidChange?(Notification(name: NSControl.textDidChangeNotification, object: search)) + host.layoutSubtreeIfNeeded() + let count = filteredTable.numberOfRows + if count > 0 { + let row = filteredTable.rect(ofRow: filteredTable.selectedRow) + #expect(filteredTable.visibleRect.contains(row)) + let viewport = try #require(filteredTable.enclosingScrollView) + #expect(viewport.contentSize.height >= min(CGFloat(count), 6) * filteredTable.rowHeight) + } + } } }