mirror of
https://github.com/claunia/marechai.git
synced 2026-07-08 17:57:08 +00:00
feat: Implement full-width search results dropdown with dynamic positioning
This commit is contained in:
@@ -155,5 +155,6 @@
|
||||
<script src="js/software-cover-batch-upload.js"></script>
|
||||
<script src="js/software-screenshot-upload.js"></script>
|
||||
<script src="js/software-screenshot-batch-upload.js"></script>
|
||||
<script src="js/search-dropdown-positioning.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
@inject SearchService SearchService
|
||||
@inject NavigationManager Nav
|
||||
@inject StringLocalizer<NavMenu> L
|
||||
@inject IJSRuntime JS
|
||||
|
||||
<div id="search-bar-anchor">
|
||||
<MudAutocomplete T="SearchResultDto"
|
||||
@bind-Value="_selected"
|
||||
@bind-Value:after="OnSelected"
|
||||
@@ -32,6 +34,7 @@
|
||||
AdornmentColor="Color.Default"
|
||||
OnAdornmentClick="OnAdornmentClick"
|
||||
OnKeyDown="OnKeyDown"
|
||||
RelativeWidth="DropdownWidth.Ignore"
|
||||
PopoverClass="search-popover-fullwidth"
|
||||
Style="background-color: rgba(255,255,255,0.15); border-radius: 4px;"
|
||||
@ref="_autocomplete">
|
||||
@@ -81,12 +84,18 @@
|
||||
</MudStack>
|
||||
</NoItemsTemplate>
|
||||
</MudAutocomplete>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
SearchResultDto _selected;
|
||||
string _lastQuery = string.Empty;
|
||||
MudAutocomplete<SearchResultDto> _autocomplete;
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if(firstRender) await JS.InvokeVoidAsync("SearchDropdownPositioning.attach", "search-bar-anchor");
|
||||
}
|
||||
|
||||
Task<IEnumerable<SearchResultDto>> SearchAsync(string value, CancellationToken ct)
|
||||
=> SearchAsyncCore(value, ct);
|
||||
|
||||
|
||||
@@ -300,10 +300,13 @@
|
||||
}
|
||||
|
||||
/* Full-width search results dropdown in top bar.
|
||||
MudAutocomplete normally anchors the popover to match the input width.
|
||||
This class overrides that to span from the search box to the right edge of the screen. */
|
||||
These are just the initial values for first paint — MudBlazor's own JS
|
||||
actively rewrites this popover's inline `left`/`width` on open, resize, and
|
||||
via its internal flip/collision logic, fighting any CSS-only override.
|
||||
js/search-dropdown-positioning.js watches this element and re-asserts
|
||||
`left` (anchored to the search box) and `right: 0; width: auto` (stretched
|
||||
to the viewport's right edge) every time MudBlazor changes it. */
|
||||
.search-popover-fullwidth {
|
||||
left: auto !important;
|
||||
right: 0 !important;
|
||||
width: auto !important;
|
||||
max-width: none !important;
|
||||
|
||||
62
Marechai/wwwroot/js/search-dropdown-positioning.js
Normal file
62
Marechai/wwwroot/js/search-dropdown-positioning.js
Normal file
@@ -0,0 +1,62 @@
|
||||
// Keeps the top-bar search results popover anchored to the search box's own
|
||||
// left edge while letting it stretch to the right edge of the viewport.
|
||||
//
|
||||
// MudAutocomplete's popover JS actively re-measures and rewrites the popover's
|
||||
// inline `left`/`width` on open, on resize, and via its own collision-avoidance
|
||||
// ("flip") logic — it does this regardless of any CSS we apply, so a CSS-only
|
||||
// `left` override gets clobbered moments after render. We instead watch the
|
||||
// popover element itself and correct its inline style back to "anchored to the
|
||||
// search box, stretched to the right edge" every time MudBlazor changes it.
|
||||
window.SearchDropdownPositioning = (function () {
|
||||
let attached = false;
|
||||
|
||||
function reposition(anchor, popover) {
|
||||
const desiredLeft = anchor.getBoundingClientRect().left + "px";
|
||||
|
||||
if (popover.style.left !== desiredLeft) popover.style.setProperty("left", desiredLeft, "important");
|
||||
if (popover.style.right !== "0px") popover.style.setProperty("right", "0px", "important");
|
||||
if (popover.style.width !== "auto") popover.style.setProperty("width", "auto", "important");
|
||||
if (popover.style.maxWidth !== "none") popover.style.setProperty("max-width", "none", "important");
|
||||
}
|
||||
|
||||
function watch(anchor, popover) {
|
||||
reposition(anchor, popover);
|
||||
|
||||
// Re-assert our positioning whenever MudBlazor's own JS touches the
|
||||
// popover's style/class (open, close, resize, flip recalculation).
|
||||
// Each call is a no-op once values already match, so this can't loop.
|
||||
new MutationObserver(() => reposition(anchor, popover))
|
||||
.observe(popover, { attributes: true, attributeFilter: ["style", "class"] });
|
||||
|
||||
window.addEventListener("resize", () => reposition(anchor, popover));
|
||||
}
|
||||
|
||||
return {
|
||||
attach: function (anchorId) {
|
||||
if (attached) return;
|
||||
|
||||
const anchor = document.getElementById(anchorId);
|
||||
if (!anchor) return;
|
||||
|
||||
const existing = document.querySelector(".search-popover-fullwidth");
|
||||
if (existing) {
|
||||
watch(anchor, existing);
|
||||
attached = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// The popover element doesn't exist until MudBlazor first renders
|
||||
// it (typically on first focus/keystroke), so wait for it to show
|
||||
// up in the DOM before attaching the real observer.
|
||||
const bodyObserver = new MutationObserver(() => {
|
||||
const popover = document.querySelector(".search-popover-fullwidth");
|
||||
if (!popover) return;
|
||||
|
||||
bodyObserver.disconnect();
|
||||
watch(anchor, popover);
|
||||
attached = true;
|
||||
});
|
||||
bodyObserver.observe(document.body, { childList: true, subtree: true });
|
||||
}
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user