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.
|
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
|
## 2.21.5 - 2026-10-27
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pulse-signage-player",
|
"name": "pulse-signage-player",
|
||||||
"version": "2.21.5",
|
"version": "2.21.6",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "Pulse Signage player application bundle",
|
"description": "Pulse Signage player application bundle",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pulse-signage-web",
|
"name": "pulse-signage-web",
|
||||||
"version": "2.21.5",
|
"version": "2.21.6",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "Pulse Signage web and bridge application bundle",
|
"description": "Pulse Signage web and bridge application bundle",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "pulse-signage",
|
"name": "pulse-signage",
|
||||||
"version": "2.21.5",
|
"version": "2.21.6",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "pulse-signage",
|
"name": "pulse-signage",
|
||||||
"version": "2.21.5",
|
"version": "2.21.6",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@better-auth/passkey": "^1.7.13",
|
"@better-auth/passkey": "^1.7.13",
|
||||||
"animate.css": "^4.1.1",
|
"animate.css": "^4.1.1",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pulse-signage",
|
"name": "pulse-signage",
|
||||||
"version": "2.21.5",
|
"version": "2.21.6",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "Pulse Signage application with MySQL and media storage",
|
"description": "Pulse Signage application with MySQL and media storage",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
@@ -191,6 +191,7 @@
|
|||||||
var container = input.closest('[data-table-search-container]') || input.closest('.card') || null;
|
var container = input.closest('[data-table-search-container]') || input.closest('.card') || null;
|
||||||
|
|
||||||
input.value = String(currentUrl.searchParams.get(searchParam) || '').trim();
|
input.value = String(currentUrl.searchParams.get(searchParam) || '').trim();
|
||||||
|
var lastSearchValue = input.value;
|
||||||
if (container) {
|
if (container) {
|
||||||
container._cancelTableSearch = function () {
|
container._cancelTableSearch = function () {
|
||||||
window.clearTimeout(pendingSearchTimer);
|
window.clearTimeout(pendingSearchTimer);
|
||||||
@@ -220,6 +221,11 @@
|
|||||||
|
|
||||||
function updateSearch() {
|
function updateSearch() {
|
||||||
var query = String(input.value || '').trim();
|
var query = String(input.value || '').trim();
|
||||||
|
lastSearchValue = query;
|
||||||
|
if (pendingSearchTimer) {
|
||||||
|
window.clearTimeout(pendingSearchTimer);
|
||||||
|
pendingSearchTimer = null;
|
||||||
|
}
|
||||||
var nextUrl = new URL(window.location.href);
|
var nextUrl = new URL(window.location.href);
|
||||||
|
|
||||||
if (query) {
|
if (query) {
|
||||||
@@ -233,6 +239,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function scheduleSearchUpdate() {
|
function scheduleSearchUpdate() {
|
||||||
|
lastSearchValue = String(input.value || '').trim();
|
||||||
if (container) {
|
if (container) {
|
||||||
container._tableRequestSequence = (container._tableRequestSequence || 0) + 1;
|
container._tableRequestSequence = (container._tableRequestSequence || 0) + 1;
|
||||||
}
|
}
|
||||||
@@ -247,7 +254,18 @@
|
|||||||
|
|
||||||
input.addEventListener('input', scheduleSearchUpdate);
|
input.addEventListener('input', scheduleSearchUpdate);
|
||||||
input.addEventListener('search', 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) {
|
input.addEventListener('keydown', function (event) {
|
||||||
if (event.key === 'Enter') {
|
if (event.key === 'Enter') {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|||||||
@@ -159,6 +159,8 @@ function createNavigationHarness() {
|
|||||||
const footer = { outerHTML: 'original footer' };
|
const footer = { outerHTML: 'original footer' };
|
||||||
const attributes = {};
|
const attributes = {};
|
||||||
const inputEvents = {};
|
const inputEvents = {};
|
||||||
|
const timers = new Map();
|
||||||
|
let nextTimerId = 0;
|
||||||
const scroller = { scrollTop: 90 };
|
const scroller = { scrollTop: 90 };
|
||||||
const input = {
|
const input = {
|
||||||
value: '',
|
value: '',
|
||||||
@@ -212,8 +214,12 @@ function createNavigationHarness() {
|
|||||||
location: { href: 'http://localhost/slides?search=demo&sort=name', assign(url) { navigations.push(url); } },
|
location: { href: 'http://localhost/slides?search=demo&sort=name', assign(url) { navigations.push(url); } },
|
||||||
history: { state: { preserved: true } },
|
history: { state: { preserved: true } },
|
||||||
addEventListener(name, handler) { events[name] = handler; },
|
addEventListener(name, handler) { events[name] = handler; },
|
||||||
clearTimeout() {},
|
clearTimeout(id) { timers.delete(id); },
|
||||||
setTimeout(handler) { events.timer = handler; return 1; }
|
setTimeout(handler) {
|
||||||
|
const id = ++nextTimerId;
|
||||||
|
timers.set(id, handler);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
for (const method of ['pushState', 'replaceState']) {
|
for (const method of ['pushState', 'replaceState']) {
|
||||||
@@ -248,7 +254,14 @@ function createNavigationHarness() {
|
|||||||
requests[index].resolve({ ok, text: async () => text });
|
requests[index].resolve({ ok, text: async () => text });
|
||||||
await new Promise(resolve => setImmediate(resolve));
|
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 () => {
|
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');
|
await harness.respond(0, 'outdated');
|
||||||
assert.equal(harness.body.outerHTML, 'original rows');
|
assert.equal(harness.body.outerHTML, 'original rows');
|
||||||
assert.equal(harness.input.value, 'new query');
|
assert.equal(harness.input.value, 'new query');
|
||||||
harness.events.timer();
|
harness.runTimers();
|
||||||
assert.equal(new URL(harness.requests[1].url).searchParams.get('page'), null);
|
assert.equal(new URL(harness.requests[1].url).searchParams.get('page'), null);
|
||||||
await harness.respond(1, 'searched');
|
await harness.respond(1, 'searched');
|
||||||
assert.equal(harness.body.outerHTML, 'searched rows');
|
assert.equal(harness.body.outerHTML, 'searched rows');
|
||||||
assert.equal(harness.history[0].method, 'replaceState');
|
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