fix(channels): picked tag chips lead the row, so the +N fold never hides an active filter
This commit is contained in:
+3
-1
@@ -301,7 +301,8 @@ Also in force:
|
|||||||
`fade-below` utility (`fade-below-panel` / `fade-above-panel` on a panel); the page band and a
|
`fade-below` utility (`fade-below-panel` / `fade-above-panel` on a panel); the page band and a
|
||||||
scrolling panel under its header fade by the scroller's own edge mask (`useScrollFade`), because what
|
scrolling panel under its header fade by the scroller's own edge mask (`useScrollFade`), because what
|
||||||
they sit on is the backdrop, not a colour. The chip rows fold their overflow into "+N" rather than
|
they sit on is the backdrop, not a colour. The chip rows fold their overflow into "+N" rather than
|
||||||
scroll, so no row has clipped ends; the carousels fade theirs. The dialog edges come with C4's pinned
|
scroll, so no row has clipped ends, and a filter row puts its picked chips first so the fold never
|
||||||
|
holds the only sign that a filter is on; the carousels fade theirs. The dialog edges come with C4's pinned
|
||||||
footer (S3f).
|
footer (S3f).
|
||||||
- **Viewport height:** `dvh`, never `vh` / `h-screen`: `100vh` is the viewport with a mobile browser's
|
- **Viewport height:** `dvh`, never `vh` / `h-screen`: `100vh` is the viewport with a mobile browser's
|
||||||
toolbar retracted, so an `h-screen` shell runs its last rows under the toolbar (contract-tested).
|
toolbar retracted, so an `h-screen` shell runs its last rows under the toolbar (contract-tested).
|
||||||
@@ -862,3 +863,4 @@ of a full-width panel and nothing on the right.
|
|||||||
| 2026-11-02 | Dragging onto a list (owner D5 a, D6 a) | The grip carries a row, or every selected row with a selected one, onto a list in the rail; the toast undoes. Many dropped inside the list stay; a coarse pointer's drawer never peeks, so its rail is a target only when pinned. | Only the grabbed row going along; the grip off under a view | One row moving out of three lit ones misleads, and a filter is how the rows to move are found |
|
| 2026-11-02 | Dragging onto a list (owner D5 a, D6 a) | The grip carries a row, or every selected row with a selected one, onto a list in the rail; the toast undoes. Many dropped inside the list stay; a coarse pointer's drawer never peeks, so its rail is a target only when pinned. | Only the grabbed row going along; the grip off under a view | One row moving out of three lit ones misleads, and a filter is how the rows to move are found |
|
||||||
| 2026-11-02 | Delete with every tick hidden (owner a) | It removes nothing and a warning says the filter hides them | Taking the row under the keyboard | While the band shows a selection, Delete means that selection: a row no one ticked must not go |
|
| 2026-11-02 | Delete with every tick hidden (owner a) | It removes nothing and a warning says the filter hides them | Taking the row under the keyboard | While the band shows a selection, Delete means that selection: a row no one ticked must not go |
|
||||||
| 2026-11-02 | Undo of a video gone further (owner a) | A video the move put in the target comes back only if the target still holds it, and a warning says how many | Everything back, whatever it did since | A move skips a video that left the source; the same rule back keeps a video out of lists the reader never put it in |
|
| 2026-11-02 | Undo of a video gone further (owner a) | A video the move put in the target comes back only if the target still holds it, and a warning says how many | Everything back, whatever it did since | A move skips a video that left the source; the same rule back keeps a video out of lists the reader never put it in |
|
||||||
|
| 2026-11-05 | Picked tag chips lead the row (owner c) | On the Channels tag row the picked chips come first, in the tags' order, then the rest | Keep the order and fold the unpicked first; mark the "+N" when it holds a pick | The row folds from its end, so a picked tag late in the list sat only inside "+N" with nothing on the row saying a filter was on |
|
||||||
|
|||||||
@@ -210,4 +210,31 @@ describe("tagFilterChips", () => {
|
|||||||
expect(chips.find((c) => c.key === "t99")?.count).toBeUndefined();
|
expect(chips.find((c) => c.key === "t99")?.count).toBeUndefined();
|
||||||
expect(chips[chips.length - 1]?.count).toBeUndefined();
|
expect(chips[chips.length - 1]?.count).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// The row folds from its end into "+N", so a picked tag late in the list was folded away: at 1024 px
|
||||||
|
// "music" sat behind "+11" with nothing on the row saying a filter was on. The picks lead the row.
|
||||||
|
it("puts the picked tags first, keeping their order, then the rest in theirs", () => {
|
||||||
|
const tag = (id: number, name: string) => ({ id, name, system: false }) as Tag;
|
||||||
|
const row = (picked: string[], noTagOn = false) =>
|
||||||
|
tagFilterChips({
|
||||||
|
userTags: [tag(1, "ai"), tag(2, "docu"), tag(3, "music"), tag(4, "nature")],
|
||||||
|
tags: { isPending: false } as never,
|
||||||
|
picked,
|
||||||
|
noTagOn,
|
||||||
|
toggle: () => {},
|
||||||
|
toggleNoTag: () => {},
|
||||||
|
t: ((k: string) => k) as never,
|
||||||
|
}).map((c) => c.label + (c.active ? "*" : ""));
|
||||||
|
expect(row(["4", "3"])).toEqual(["music*", "nature*", "ai", "docu", "channels.tags.noTag"]);
|
||||||
|
expect(row(["99", "3"])).toEqual([
|
||||||
|
"99*",
|
||||||
|
"music*",
|
||||||
|
"ai",
|
||||||
|
"docu",
|
||||||
|
"nature",
|
||||||
|
"channels.tags.noTag",
|
||||||
|
]);
|
||||||
|
expect(row([], true)).toEqual(["channels.tags.noTag*", "ai", "docu", "music", "nature"]);
|
||||||
|
expect(row([])).toEqual(["ai", "docu", "music", "nature", "channels.tags.noTag"]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -51,11 +51,12 @@ export function tagsColumnTest(c: ManagedChannel, values: string[]): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** The manager's tag-filter chip row (the Tags column filter's values): any picked id that is not one
|
/** The manager's tag-filter chip row (the Tags column filter's values): any picked id that is not one
|
||||||
* of the user's tags, then each user tag, then the "No tag" sentinel. A stored pick can outlive its
|
* of the user's tags, then the picked ones of the user's tags and "No tag", then the rest, each group
|
||||||
|
* in the tags' order with "No tag" last. The row folds from its end into "+N", so every pick leads it:
|
||||||
|
* a filter that is on stays in sight, never only inside the fold. A stored pick can outlive its
|
||||||
* tag (deleted here or on another device) and still narrows the table to nothing, so it stays a
|
* tag (deleted here or on another device) and still narrows the table to nothing, so it stays a
|
||||||
* visible, removable chip — FIRST, so the row's "+N" overflow never folds the reason away —
|
* visible, removable chip, and so does every pick while the tag list loads or after it failed, named
|
||||||
* and so does every pick while the tag list loads or after it failed, named by lib/tagPicks' rule
|
* by lib/tagPicks' rule (a placeholder, then the name, "Deleted tag" or the id), not hidden. */
|
||||||
* (a placeholder, then the name, "Deleted tag" or the id), not hidden. */
|
|
||||||
export function tagFilterChips(o: {
|
export function tagFilterChips(o: {
|
||||||
userTags: Tag[];
|
userTags: Tag[];
|
||||||
tags: TagList;
|
tags: TagList;
|
||||||
@@ -67,14 +68,7 @@ export function tagFilterChips(o: {
|
|||||||
}): FilterChip[] {
|
}): FilterChip[] {
|
||||||
const mine = new Set(o.userTags.map((tg) => String(tg.id)));
|
const mine = new Set(o.userTags.map((tg) => String(tg.id)));
|
||||||
const others = o.picked.filter((id) => id !== NO_TAG_SENTINEL && !mine.has(id));
|
const others = o.picked.filter((id) => id !== NO_TAG_SENTINEL && !mine.has(id));
|
||||||
return [
|
const known: FilterChip[] = [
|
||||||
...others.map((id) => ({
|
|
||||||
key: `t${id}`,
|
|
||||||
label: tagPickLabel(Number(id), o.tags, o.t),
|
|
||||||
pending: o.tags.isPending,
|
|
||||||
active: true,
|
|
||||||
onToggle: () => o.toggle(Number(id)),
|
|
||||||
})),
|
|
||||||
...o.userTags.map((tg) => ({
|
...o.userTags.map((tg) => ({
|
||||||
key: `t${tg.id}`,
|
key: `t${tg.id}`,
|
||||||
label: tg.name,
|
label: tg.name,
|
||||||
@@ -89,6 +83,17 @@ export function tagFilterChips(o: {
|
|||||||
onToggle: o.toggleNoTag,
|
onToggle: o.toggleNoTag,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
return [
|
||||||
|
...others.map((id) => ({
|
||||||
|
key: `t${id}`,
|
||||||
|
label: tagPickLabel(Number(id), o.tags, o.t),
|
||||||
|
pending: o.tags.isPending,
|
||||||
|
active: true,
|
||||||
|
onToggle: () => o.toggle(Number(id)),
|
||||||
|
})),
|
||||||
|
...known.filter((c) => c.active),
|
||||||
|
...known.filter((c) => !c.active),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Everything the Channels-manager column set closes over. The manager owns the state + mutations;
|
/** Everything the Channels-manager column set closes over. The manager owns the state + mutations;
|
||||||
|
|||||||
Reference in New Issue
Block a user