swarm-ui: keep the table filter popover inside the window
This commit is contained in:
parent
c2f17799fc
commit
16c9e0ae3b
3 changed files with 53 additions and 2 deletions
|
|
@ -9,12 +9,19 @@
|
|||
// a `<table>` doesn't shrink below its content's natural width on its
|
||||
// own, so without this the page itself would break, not just look
|
||||
// cramped.
|
||||
import { useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
import {
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "preact/hooks";
|
||||
import { createPortal } from "preact/compat";
|
||||
import type { ComponentChildren } from "preact";
|
||||
import { FilterIcon } from "@hive/shared/icons.js";
|
||||
import { useLocalSetting } from "@hive/shared/settings-storage.js";
|
||||
import { MultiselectFilterOptions } from "../multiselect-filter/MultiselectFilter.js";
|
||||
import { clampLeft } from "./popover-position.js";
|
||||
import "./Table.css";
|
||||
|
||||
export interface TableColumn<T> {
|
||||
|
|
@ -299,6 +306,13 @@ export function Table<T>({
|
|||
// effect below can recompute the *currently open* column's position
|
||||
// without needing the click that opened it to have happened again.
|
||||
const thRefs = useRef<Map<string, HTMLTableCellElement>>(new Map());
|
||||
// The open popover's own width, measured once it is on screen, so its
|
||||
// left edge can be pulled back inside the window.
|
||||
const popoverRef = useRef<HTMLDivElement>(null);
|
||||
const [popoverWidth, setPopoverWidth] = useState(0);
|
||||
useLayoutEffect(() => {
|
||||
setPopoverWidth(popoverRef.current?.offsetWidth ?? 0);
|
||||
}, [openFilterKey, popoverPos]);
|
||||
|
||||
function computePopoverPos(key: string) {
|
||||
const th = thRefs.current.get(key);
|
||||
|
|
@ -579,10 +593,11 @@ export function Table<T>({
|
|||
class="ui-table-filter-popover"
|
||||
role="dialog"
|
||||
aria-label={filterLabel(openColumn)}
|
||||
ref={popoverRef}
|
||||
style={{
|
||||
position: "fixed",
|
||||
top: `${popoverPos.top}px`,
|
||||
left: `${popoverPos.left}px`,
|
||||
left: `${clampLeft(popoverPos.left, popoverWidth, window.innerWidth)}px`,
|
||||
}}
|
||||
>
|
||||
{renderFilterControl(openColumn)}
|
||||
|
|
|
|||
19
frontend/packages/swarm-ui/src/ui/table/popover-position.ts
Normal file
19
frontend/packages/swarm-ui/src/ui/table/popover-position.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// Where a table's filter popover sits, without a DOM.
|
||||
|
||||
// Space kept between a floating box and the viewport's edge.
|
||||
export const POPOVER_EDGE_PX = 8;
|
||||
|
||||
/**
|
||||
* The left edge for a popover of `width` that wants to start at `left`:
|
||||
* pulled in so its right edge stays on screen, but never past the left edge.
|
||||
* A filterable column near the right of the window would otherwise open
|
||||
* partly outside it.
|
||||
*/
|
||||
export function clampLeft(
|
||||
left: number,
|
||||
width: number,
|
||||
viewportWidth: number,
|
||||
): number {
|
||||
const maxLeft = viewportWidth - width - POPOVER_EDGE_PX;
|
||||
return Math.max(POPOVER_EDGE_PX, Math.min(left, maxLeft));
|
||||
}
|
||||
17
frontend/packages/swarm-ui/test/popover-position.test.mjs
Normal file
17
frontend/packages/swarm-ui/test/popover-position.test.mjs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// Where a table's filter popover sits. Run by `npm test`, which Node runs
|
||||
// with its built-in test runner and type stripping, so the `.ts` module is
|
||||
// imported as it is.
|
||||
import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
import { clampLeft } from "../src/ui/table/popover-position.ts";
|
||||
|
||||
test("a popover near the right edge is pulled back on screen", () => {
|
||||
// 300 wide in a 1000 wide window: the right edge may reach 992.
|
||||
assert.equal(clampLeft(900, 300, 1000), 692);
|
||||
assert.equal(clampLeft(100, 300, 1000), 100);
|
||||
});
|
||||
|
||||
test("a popover never goes past the left edge", () => {
|
||||
assert.equal(clampLeft(5, 300, 1000), 8);
|
||||
assert.equal(clampLeft(50, 2000, 1000), 8);
|
||||
});
|
||||
Loading…
Reference in a new issue