From 4c1b78408f8acf2d9dc513b99626454a04989417 Mon Sep 17 00:00:00 2001 From: rays Date: Mon, 19 Oct 2026 14:55:25 +0000 Subject: [PATCH] An x in a search box clears it (#291) Once something is typed: at the box's right end, inside it. One click handler for every box, which clears it and fires its input event, so each box's own handler brings its list back whole. The browser's own x, drawn by some browsers and not others, gives way to it. Co-Authored-By: Claude Opus 5.5 --- tests/ui/app.spec.js | 16 ++++++++++++++++ web/CHANGELOG.md | 1 + web/app.css | 17 ++++++++++++++--- web/index.html | 4 ++-- web/src/admin.ts | 2 +- web/src/signals.ts | 2 +- web/src/util.ts | 14 ++++++++++++++ 7 files changed, 49 insertions(+), 7 deletions(-) diff --git a/tests/ui/app.spec.js b/tests/ui/app.spec.js index 207752f..f51d07d 100644 --- a/tests/ui/app.spec.js +++ b/tests/ui/app.spec.js @@ -1951,6 +1951,22 @@ test('a page brought back into view catches up with what was read elsewhere', as await expect(badge).toHaveText(String(before - 1)); }); +test('a search box has an x that clears it once something is typed', async ({ page }) => { + // #291: inside the box, at its right end, and the list comes back whole. + const box = page.locator('#feedFilter'); + const x = page.locator('.searchwrap .clearx'); + const all = await page.locator('#feedlist .feed').count(); + await expect(x).toBeHidden(); + await box.fill('zzz-nothing'); + await expect(x).toBeVisible(); + await expect(page.locator('#feedlist .feed')).toHaveCount(0); + await x.click(); + await expect(box).toHaveValue(''); + await expect(box).toBeFocused(); + await expect(x).toBeHidden(); + await expect(page.locator('#feedlist .feed')).toHaveCount(all); +}); + test('a feed\'s settings say what downloads, take words as chips, and keep what is saved', async ({ page }) => { // The redesigned dialog (#167): the download choice, word chips, the fold, and a save that holds. const open = async () => { diff --git a/web/CHANGELOG.md b/web/CHANGELOG.md index 0316ada..1e804d1 100644 --- a/web/CHANGELOG.md +++ b/web/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- A search box has an x at its right end once something is typed in it, which clears it: items, the feed list, the log in Admin and a Signal's icons (#291). - A feed's, an OPML subscription's or a Patreon creator's settings have In All Subscriptions: off keeps its items out of All Subscriptions and its unread count, still there on its own page, in Signals and in Currently Listening (#288). - A feed's settings have a Signals switch beside All Subscriptions, to keep its items out of every Signal (#289). - An item sent as both HTML and Markdown, as Scripting News's are, has a choice beside its buttons to show it as the publisher's HTML or as the Markdown, kept from one item to the next (#285). diff --git a/web/app.css b/web/app.css index 6a1744c..7b9f62e 100644 --- a/web/app.css +++ b/web/app.css @@ -537,7 +537,7 @@ a{color:var(--accent)} position:sticky;top:0; } #topbar .grow{flex:1} -#topbar #epSearch{width:260px;flex:none} +#topbar #epBox{width:260px;flex:none} .tgroup{display:flex;flex:none;border:1px solid var(--line);border-radius:8px;overflow:hidden;background:var(--panel2)} .tgroup button,.tgroup a{padding:5px 11px;min-width:34px;color:var(--dim);font-size:14px;line-height:1.3} .tgroup a{display:inline-grid;place-items:center} @@ -567,6 +567,17 @@ input[type=search],input[type=text],input[type=password],input[type=number],sele border-radius:8px;padding:7px 10px;font:inherit;font-size:13.5px; } input:focus,select:focus{outline:0;border-color:var(--accent)} +/* A search box and the x that clears it, inside its right end, there only once something is typed + (#291). The browser's own x, in some browsers and not others, gives way to it. */ +.clearable{position:relative;display:flex;align-items:center;min-width:0} +/* With [type=search], or the phone's padding for every text box, later on, takes the room back. */ +.clearable>input[type=search]{flex:1;min-width:0;padding-right:32px} +.clearable>input::-webkit-search-cancel-button{-webkit-appearance:none;appearance:none} +.clearx{position:absolute;right:4px;top:50%;transform:translateY(-50%);display:grid;place-items:center;width:26px;height:26px;padding:0;border-radius:50%;color:var(--faint)} +.clearx:hover{color:var(--fg)} +.clearx svg{width:11px;height:11px} +.clearable:has(>input:placeholder-shown) .clearx{display:none} +.inline .clearable{flex:1} /* The wider left gutter is the folder triangle's; everything in the list shifts with it, so the feeds still line up with the places above. */ #feedlist{overflow-y:auto;padding:0 8px 12px 16px;flex:1;min-height:0} @@ -1251,7 +1262,7 @@ body:has(#player.on) #status{padding-bottom:3px} padding-left:calc(8px + var(--safe-l));padding-right:calc(8px + var(--safe-r)); } #topbar .grow,.tgroup.item{display:none} - #topbar #epSearch{width:auto;flex:1;min-width:0} + #topbar #epBox{width:auto;flex:1;min-width:0} .tgroup button,.tgroup a{padding:4px 8px;min-width:32px} .fhead.slim .acts{flex:1 1 100%} .iconbtn{width:38px;height:38px} @@ -1359,7 +1370,7 @@ body:has(#player.on) #status{padding-bottom:3px} input[type=search],input[type=text],input[type=password],input[type=number],select{ font-size:17px;background:var(--panel2);border-color:var(--edge);border-radius:10px;padding:9px 12px; } - #topbar #epSearch{font-size:16px;border-radius:999px;padding:9px 14px} + #topbar #epSearch{font-size:16px;border-radius:999px;padding:9px 34px 9px 14px} input:focus,select:focus{border-color:var(--accent)} /* The feed's name as the page's large title, the way Podcasts heads a show. */ diff --git a/web/index.html b/web/index.html index f7eb5ca..d944f26 100644 --- a/web/index.html +++ b/web/index.html @@ -28,7 +28,7 @@ - +
@@ -36,7 +36,7 @@