Холст доски: высота слоя измерения, сужение выборки, ширина полосы дат (#21)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-10-14 23:09:47 +04:00
parent 86f5ccc981
commit 089c4fb78d
4 changed files with 32 additions and 6 deletions

View File

@ -157,5 +157,5 @@ td.k{font-family:var(--font-mono);font-size:12px;white-space:nowrap}
.card .open-card:hover,.card .open-card:focus-visible{color:var(--accent);outline:none}
/* слой измерения: карточки шириной CW с высотой по содержимому, невидим */
.measure{position:absolute;left:-100000px;top:0;visibility:hidden;pointer-events:none}
.measure .card{position:static}
.measure .card{position:static;height:auto}
.measure .card .title{-webkit-line-clamp:unset}

View File

@ -1,3 +1,4 @@
import { useState } from "react";
import { fireEvent, screen, within } from "@testing-library/react";
import { readFileSync } from "node:fs";
import { join } from "node:path";
@ -85,4 +86,29 @@ describe("BoardCanvas", () => {
draw({ vis: computeVisible(model, { ...noFilters(), who: new Set(["Никто"]) }, [], "2026-10-14"), emptyText: "Фильтры не оставили" });
expect(screen.getByText("Фильтры не оставили")).toBeInTheDocument();
});
it("сужение выборки при смене фильтра не роняет холст (карточки старой раскладки пропускаются)", () => {
const wide = computeVisible(model, noFilters(), [], "2026-10-14");
const narrow = computeVisible(model, { ...noFilters(), who: new Set(["Иванов Иван Иванович"]) }, [], "2026-10-14");
expect(narrow.nodes.length).toBeLessThan(wide.nodes.length);
function Harness() {
const [vis, setVis] = useState(wide);
return (
<>
<button onClick={() => setVis(narrow)}>сузить</button>
<BoardCanvas vis={vis} byDate={false} snap today="2026-10-14" store={store} sprintSel={new Set<string>()} emptyText="пусто"
onToggleLane={vi.fn()} onMoveLane={vi.fn()} onColWidth={vi.fn()} />
</>
);
}
renderWithProviders(<Harness />);
expect(board().querySelectorAll(".card[data-key]")).toHaveLength(wide.nodes.length);
fireEvent.click(screen.getByText("сузить"));
expect(board().querySelectorAll(".card[data-key]").length).toBeLessThanOrEqual(narrow.nodes.length);
});
it("полоса дат получает ширину обёртки доски", () => {
draw({ byDate: true });
expect((document.querySelector(".dates-in") as HTMLElement).style.width).toMatch(/px$|^0/);
});
});

View File

@ -135,7 +135,7 @@ export function BoardCanvas({ vis, byDate, snap, today, store, sprintSel, emptyT
return (
<>
{byDate && !empty && layout?.dateCols && placed && (
<DatesBar cols={layout.dateCols} width={placed.width} scrollLeft={scroll.left} inRef={datesInRef} onColWidth={onColWidth} />
<DatesBar cols={layout.dateCols} width={placed.width} scrollLeft={scroll.left} wrapW={scroll.wrapW} inRef={datesInRef} onColWidth={onColWidth} />
)}
<div className={"stage" + (byDate && !empty ? " bydate" : "")}>
<div className="wrap" ref={wrapRef} onScroll={syncScroll}>
@ -153,7 +153,7 @@ export function BoardCanvas({ vis, byDate, snap, today, store, sprintSel, emptyT
targetClass={dragTarget && dragTarget.to === i ? (i < dragTarget.from ? "target" : i > dragTarget.from ? "target-after" : "") : ""}
onDragTarget={(to) => setDragTarget(to === null ? null : { from: i, to })} />
))}
{Object.entries(placed.pos).map(([k, box]) => (
{Object.entries(placed.pos).filter(([k]) => byKey[k]).map(([k, box]) => (
<Card key={k} node={byKey[k]} box={box} rel={rel[k]} multiSprint={multiSprint} sprintSel={sprintSel}
onPointerDown={onCardDown} onAutoSize={onAutoSize} />
))}

View File

@ -3,8 +3,8 @@ import { COLW, GRID } from "../constants.ts";
import { dateText, dateTitle, type DateCol } from "../layout/layout.ts";
// липкая полоса подписей дат над доской: сдвиг — как у горизонтальной прокрутки доски; ручка границы меняет ширину колонки
export function DatesBar({ cols, width, scrollLeft, inRef, onColWidth }: {
cols: DateCol[]; width: number; scrollLeft: number; inRef: RefObject<HTMLDivElement | null>;
export function DatesBar({ cols, width, scrollLeft, wrapW, inRef, onColWidth }: {
cols: DateCol[]; width: number; scrollLeft: number; wrapW: number; inRef: RefObject<HTMLDivElement | null>;
onColWidth(date: string, w: number | null): void;
}) {
const raf = useRef(0);
@ -28,7 +28,7 @@ export function DatesBar({ cols, width, scrollLeft, inRef, onColWidth }: {
const cls = (c: DateCol) => (c.cls ? " " + c.cls : "");
return (
<div className="dates-bar">
<div className="dates-in" ref={inRef}>
<div className="dates-in" ref={inRef} style={{ width: wrapW }}>
<div className="dates-track" style={{ width, transform: `translateX(${-scrollLeft}px)` }}>
{cols.map((c) => (
<Fragment key={c.date}>