Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions src/core/layout.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,38 @@ describe('listView', () => {
}
});

it('shows the last item instead of a "1 more" indicator (second-from-bottom selected)', () => {
// 画面が埋まった状態で下から2番目を選ぶと、従来は末尾1件が「↓ 他 1 件」に
// 化けていた。インジケータではなくその実項目を表示する。
const v = listView(10, 8, 5);
expect(v.end).toBe(10); // 末尾の項目まで表示
expect(v.showBelow).toBe(false); // 「↓ 他 1 件」は出さない
expect(v.hiddenBelow).toBe(0);
expect(8).toBeGreaterThanOrEqual(v.start);
expect(8).toBeLessThan(v.end);
expect(renderedRows(v)).toBe(5); // 描画行数は cap のまま
});

it('never shows an indicator that hides only one item (shows the item instead)', () => {
for (const total of [6, 7, 10, 15, 30]) {
for (let cap = 2; cap <= 9; cap++) {
for (let sel = 0; sel < total; sel++) {
const v = listView(total, sel, cap);
const at = `total=${total} cap=${cap} sel=${sel}`;
if (v.showBelow) {
expect(v.hiddenBelow, `${at} below`).toBeGreaterThan(1);
}
if (v.showAbove) {
expect(v.hiddenAbove, `${at} above`).toBeGreaterThan(1);
}
expect(sel, `${at} start`).toBeGreaterThanOrEqual(v.start);
expect(sel, `${at} end`).toBeLessThan(v.end);
expect(renderedRows(v), `${at} rows`).toBeLessThanOrEqual(cap);
}
}
}
});

it('never overflows a tiny cap (drops indicators, keeps one content row)', () => {
const v1 = listView(10, 5, 1);
expect(v1.end - v1.start).toBe(1);
Expand Down
26 changes: 22 additions & 4 deletions src/core/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ export interface ListView {
* 予約するため、描画行数(項目 + インジケータ)は常に `cap` 以下になる。
* 選択はウィンドウ下端寄りにアンカーする(下へ動かすとスクロールする挙動。
* コンポーザの {@link visibleLineRange} と同じ)。
*
* ただし端に隠れているのが 1 件だけの場合は、インジケータ(「他 1 件」)を出さず
* その項目自体を表示する。インジケータも実項目も 1 行なので描画行数は変わらず、
* 「1 件を隠して代わりに 1 行のインジケータを出す」より実項目を見せたほうがよい
* (下から 2 番目を選ぶと最後の 1 件が「↓ 他 1 件」に化ける、を防ぐ)。
*/
export function listView(total: number, selected: number, cap: number): ListView {
const c = Math.max(1, Math.floor(cap));
Expand Down Expand Up @@ -132,11 +137,24 @@ export function listView(total: number, selected: number, cap: number): ListView
showAbove = false;
}
}
const hiddenAbove = win.start;
const hiddenBelow = total - win.end;
// 隠れているのが 1 件だけの端は、インジケータ用に予約した 1 行へその項目を
// 直接出す(描画行数は不変)。両端が同時に 1 件になることは overflow 時には
// 起きない(それは total === cap を意味し、その場合は上で早期 return 済み)。
let start = win.start;
let end = win.end;
if (showBelow && total - end === 1) {
end += 1;
showBelow = false;
}
if (showAbove && start === 1) {
start -= 1;
showAbove = false;
}
const hiddenAbove = start;
const hiddenBelow = total - end;
return {
start: win.start,
end: win.end,
start,
end,
hiddenAbove,
hiddenBelow,
showAbove: showAbove && hiddenAbove > 0,
Expand Down
Loading