Release 2.21.6: prevent duplicate list searches
This commit is contained in:
1 parent
9e988c307e
commit
675483624b
7 files changed
+66
-10
No files matched your search
@@ -2,6 +2,12 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## 2.21.6 - 2026-10-27
|
||||
|
||||
### Fixed
|
||||
|
||||
- List searches no longer issue duplicate requests when the search field loses focus, avoiding intermittent missed clicks on refreshed row buttons.
|
||||
|
||||
## 2.21.5 - 2026-10-27
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pulse-signage-player",
|
||||
"version": "2.21.5",
|
||||
"version": "2.21.6",
|
||||
"private": false,
|
||||
"description": "Pulse Signage player application bundle",
|
||||
"engines": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pulse-signage-web",
|
||||
"version": "2.21.5",
|
||||
"version": "2.21.6",
|
||||
"private": false,
|
||||
"description": "Pulse Signage web and bridge application bundle",
|
||||
"engines": {
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "pulse-signage",
|
||||
"version": "2.21.5",
|
||||
"version": "2.21.6",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "pulse-signage",
|
||||
"version": "2.21.5",
|
||||
"version": "2.21.6",
|
||||
"dependencies": {
|
||||
"@better-auth/passkey": "^1.7.13",
|
||||
"animate.css": "^4.1.1",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pulse-signage",
|
||||
"version": "2.21.5",
|
||||
"version": "2.21.6",
|
||||
"private": false,
|
||||
"description": "Pulse Signage application with MySQL and media storage",
|
||||
"engines": {
|
||||
|
||||
@@ -191,6 +191,7 @@
|
||||
var container = input.closest('[data-table-search-container]') || input.closest('.card') || null;
|
||||
|
||||
input.value = String(currentUrl.searchParams.get(searchParam) || '').trim();
|
||||
var lastSearchValue = input.value;
|
||||
if (container) {
|
||||
container._cancelTableSearch = function () {
|
||||
window.clearTimeout(pendingSearchTimer);
|
||||
@@ -220,6 +221,11 @@
|
||||
|
||||
function updateSearch() {
|
||||
var query = String(input.value || '').trim();
|
||||
lastSearchValue = query;
|
||||
if (pendingSearchTimer) {
|
||||
window.clearTimeout(pendingSearchTimer);
|
||||
pendingSearchTimer = null;
|
||||
}
|
||||
var nextUrl = new URL(window.location.href);
|
||||
|
||||
if (query) {
|
||||
@@ -233,6 +239,7 @@
|
||||
}
|
||||
|
||||
function scheduleSearchUpdate() {
|
||||
lastSearchValue = String(input.value || '').trim();
|
||||
if (container) {
|
||||
container._tableRequestSequence = (container._tableRequestSequence || 0) + 1;
|
||||
}
|
||||
@@ -247,7 +254,18 @@
|
||||
|
||||
input.addEventListener('input', scheduleSearchUpdate);
|
||||
input.addEventListener('search', scheduleSearchUpdate);
|
||||
input.addEventListener('change', updateSearch);
|
||||
input.addEventListener('change', function () {
|
||||
var query = String(input.value || '').trim();
|
||||
if (query === lastSearchValue) {
|
||||
if (pendingSearchTimer) {
|
||||
window.clearTimeout(pendingSearchTimer);
|
||||
pendingSearchTimer = null;
|
||||
updateSearch();
|
||||
}
|
||||
return;
|
||||
}
|
||||
updateSearch();
|
||||
});
|
||||
input.addEventListener('keydown', function (event) {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -159,6 +159,8 @@ function createNavigationHarness() {
|
||||
const footer = { outerHTML: 'original footer' };
|
||||
const attributes = {};
|
||||
const inputEvents = {};
|
||||
const timers = new Map();
|
||||
let nextTimerId = 0;
|
||||
const scroller = { scrollTop: 90 };
|
||||
const input = {
|
||||
value: '',
|
||||
@@ -212,8 +214,12 @@ function createNavigationHarness() {
|
||||
location: { href: 'http://localhost/slides?search=demo&sort=name', assign(url) { navigations.push(url); } },
|
||||
history: { state: { preserved: true } },
|
||||
addEventListener(name, handler) { events[name] = handler; },
|
||||
clearTimeout() {},
|
||||
setTimeout(handler) { events.timer = handler; return 1; }
|
||||
clearTimeout(id) { timers.delete(id); },
|
||||
setTimeout(handler) {
|
||||
const id = ++nextTimerId;
|
||||
timers.set(id, handler);
|
||||
return id;
|
||||
}
|
||||
}
|
||||
};
|
||||
for (const method of ['pushState', 'replaceState']) {
|
||||
@@ -248,7 +254,14 @@ function createNavigationHarness() {
|
||||
requests[index].resolve({ ok, text: async () => text });
|
||||
await new Promise(resolve => setImmediate(resolve));
|
||||
}
|
||||
return { sandbox, container, events, requests, navigations, history, body, footer, attributes, input, inputEvents, scroller, click, respond };
|
||||
return {
|
||||
sandbox, container, events, requests, navigations, history, body, footer, attributes, input, inputEvents, scroller, click, respond,
|
||||
runTimers() {
|
||||
const pending = Array.from(timers.values());
|
||||
timers.clear();
|
||||
pending.forEach(handler => handler());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test('pagination replaces only table results and pushes a URL preserving search and sort', async () => {
|
||||
@@ -319,9 +332,28 @@ test('typing invalidates pending pagination and search still resets to page one'
|
||||
await harness.respond(0, 'outdated');
|
||||
assert.equal(harness.body.outerHTML, 'original rows');
|
||||
assert.equal(harness.input.value, 'new query');
|
||||
harness.events.timer();
|
||||
harness.runTimers();
|
||||
assert.equal(new URL(harness.requests[1].url).searchParams.get('page'), null);
|
||||
await harness.respond(1, 'searched');
|
||||
assert.equal(harness.body.outerHTML, 'searched rows');
|
||||
assert.equal(harness.history[0].method, 'replaceState');
|
||||
});
|
||||
|
||||
test('blur flushes a pending search once and does not repeat an active search', async () => {
|
||||
const pendingHarness = createNavigationHarness();
|
||||
pendingHarness.input.value = 'button race';
|
||||
pendingHarness.inputEvents.input();
|
||||
pendingHarness.inputEvents.change();
|
||||
assert.equal(pendingHarness.requests.length, 1);
|
||||
pendingHarness.runTimers();
|
||||
assert.equal(pendingHarness.requests.length, 1);
|
||||
|
||||
const activeHarness = createNavigationHarness();
|
||||
activeHarness.input.value = 'button race';
|
||||
activeHarness.inputEvents.input();
|
||||
activeHarness.runTimers();
|
||||
assert.equal(activeHarness.requests.length, 1);
|
||||
activeHarness.inputEvents.change();
|
||||
assert.equal(activeHarness.requests.length, 1);
|
||||
await activeHarness.respond(0, 'searched');
|
||||
});
|
||||
Reference in new issue
Block a user