Skip to content

Commit

Permalink
feat: improve string column search dialog
Browse files Browse the repository at this point in the history
  • Loading branch information
thinkh committed Feb 15, 2024
1 parent b2e37f2 commit 7183b44
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 22 deletions.
43 changes: 43 additions & 0 deletions src/styles/_dialogs.scss
Original file line number Diff line number Diff line change
Expand Up @@ -265,3 +265,46 @@
bottom: -$arrow_size;
}
}

/* search dialog for string columns */
.#{$lu_css_prefix}-string-search-dialog {
display: flex;
gap: 5px;

.#{$lu_css_prefix}-search-count {
color: $lu_toolbar_color_base;
}

button {
border: none;
background: none;
color: $lu_toolbar_color_base2;

&:disabled {
color: $lu_toolbar_color_base;
}

&:not(:disabled) {
cursor: pointer;

&:hover,
&:active,
&:focus {
color: $lu_toolbar_color_hover;
}
}

&::before {
@include lu_icons();
}

&.#{$lu_css_prefix}-previous-result::before {
content: $lu_icon_up_open;
}

&.#{$lu_css_prefix}-next-result::before {
content: $lu_icon_down_open;
}

}
}
126 changes: 104 additions & 22 deletions src/ui/dialogs/SearchDialog.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Column } from '../../model';
import type { IDataProvider } from '../../provider';
import ADialog, { type IDialogContext } from './ADialog';
import { cssClass } from '../../styles';
import { aria, cssClass } from '../../styles';
import { debounce } from '../../internal';

/** @internal */
Expand All @@ -24,9 +24,17 @@ export default class SearchDialog extends ADialog {
}

protected build(node: HTMLElement) {
// NOTE: the next button is of type submit to enable jumping to the next search result with the enter key in the search input field
node.insertAdjacentHTML(
'beforeend',
`<input type="text" size="20" value="" required autofocus placeholder="search... (>= 3 chars)">
`<div class="${cssClass('string-search-dialog')}">
<input type="text" size="20" value="" required autofocus placeholder="Enter a search term...">
<div class="${cssClass('search-count')}" hidden>
<span class="${cssClass('search-current')}"></span>/<span class="${cssClass('search-total')}"></span>
</div>
<button type="button" class="${cssClass('previous-result')}" title="Previous search result" disabled>${aria('Previous search result')}</button>
<button type="submit" class="${cssClass('next-result')}" title="Next search result" disabled>${aria('Next search result')}</button>
</div>
<label class="${cssClass('checkbox')}">
<input type="checkbox">
<span>Use regular expressions</span>
Expand All @@ -36,68 +44,142 @@ export default class SearchDialog extends ADialog {

const input = node.querySelector<HTMLInputElement>('input[type="text"]')!;
const checkbox = node.querySelector<HTMLInputElement>('input[type="checkbox"]')!;
const previous = node.querySelector<HTMLButtonElement>(`.${cssClass('previous-result')}`)!;
const next = node.querySelector<HTMLButtonElement>(`.${cssClass('next-result')}`)!;

const update = () => {
const search: any = input.value;
if (search.length < 3) {
input.setCustomValidity('at least 3 characters');
if (search.length === 0) {
input.setCustomValidity('Enter a search term');
return;
}
input.setCustomValidity('');
};

input.addEventListener('input', update, {
passive: true,
});

checkbox.addEventListener('change', update, {
passive: true,
});

previous.addEventListener('click', (evt) => {
evt.preventDefault();
evt.stopPropagation();
this.jumpToPreviousResult();
});

next.addEventListener('click', (evt) => {
evt.preventDefault();
evt.stopPropagation();
this.jumpToNextResult();
});

this.enableLivePreviews([input, checkbox]);

if (!this.showLivePreviews()) {
return;
}

input.addEventListener(
'input',
debounce(() => this.submit(), 100),
debounce(() => this.searchAndJump(), 100),
{
passive: true,
}
);
}

protected submit() {
private jumpToPreviousResult() {
if (this.current) {
const searchCount = this.find<HTMLElement>(`.${cssClass('search-count')}`)!;
const current = searchCount.querySelector(`.${cssClass('search-current')}`)!;

Check failure on line 98 in src/ui/dialogs/SearchDialog.ts

View workflow job for this annotation

GitHub Actions / build

Delete `······`

Check failure on line 98 in src/ui/dialogs/SearchDialog.ts

View workflow job for this annotation

GitHub Actions / build

Delete `······`
const previous = (this.current.index - 1 + this.current.indices.length) % this.current.indices.length;
this.current.index = previous;
current.textContent = String(previous + 1);
this.provider.jumpToNearest([this.current.indices[previous]]);
}
}

private jumpToNextResult() {
if (this.current) {
const searchCount = this.find<HTMLElement>(`.${cssClass('search-count')}`)!;
const current = searchCount.querySelector(`.${cssClass('search-current')}`)!;

Check failure on line 110 in src/ui/dialogs/SearchDialog.ts

View workflow job for this annotation

GitHub Actions / build

Delete `······`

Check failure on line 110 in src/ui/dialogs/SearchDialog.ts

View workflow job for this annotation

GitHub Actions / build

Delete `······`
const next = (this.current.index + 1) % this.current.indices.length;
this.current.index = next;
current.textContent = String(next + 1);
this.provider.jumpToNearest([this.current.indices[next]]);
}
}

private searchAndJump() {
const input = this.findInput('input[type="text"]')!;
const checkbox = this.findInput('input[type="checkbox"]')!;
const previous = this.find<HTMLButtonElement>(`.${cssClass('previous-result')}`)!;
const next = this.find<HTMLButtonElement>(`.${cssClass('next-result')}`)!;
const searchCount = this.find<HTMLElement>(`.${cssClass('search-count')}`)!;

let search: string = input.value;
const isRegex = checkbox.checked;
if (this.current && this.current.search === search && this.current.isRegex === isRegex) {
const next = (this.current.index + 1) % this.current.indices.length;
this.current.index = next;
this.provider.jumpToNearest([this.current.indices[next]]);

Check failure on line 127 in src/ui/dialogs/SearchDialog.ts

View workflow job for this annotation

GitHub Actions / build

Delete `····`

Check failure on line 127 in src/ui/dialogs/SearchDialog.ts

View workflow job for this annotation

GitHub Actions / build

Delete `····`
if (search.length === 0) {
this.current = null;
previous.disabled = true;
next.disabled = true;
searchCount.hidden = true;
return false;
}

const indices = this.provider.searchAndJump(isRegex ? new RegExp(search) : search, this.column, true);
if (indices) {
this.current = {
search,
isRegex,
indices,
index: 0,
};

const current = searchCount.querySelector(`.${cssClass('search-current')}`)!;
const total = searchCount.querySelector(`.${cssClass('search-total')}`)!;
current.textContent = String(indices.length < 1 ? this.current.index : this.current.index + 1);
total.textContent = String(indices.length);
searchCount.hidden = false;

previous.disabled = indices.length < 1;
next.disabled = indices.length < 1;

return indices.length > 1;

Check failure on line 154 in src/ui/dialogs/SearchDialog.ts

View workflow job for this annotation

GitHub Actions / build

Delete `⏎······`

Check failure on line 154 in src/ui/dialogs/SearchDialog.ts

View workflow job for this annotation

GitHub Actions / build

Delete `⏎······`

} else {
const indices = this.provider.searchAndJump(isRegex ? new RegExp(search) : search, this.column, true);
if (indices) {
this.current = {
search,
isRegex,
indices,
index: 0,
};
return indices.length > 1;
} else {
this.current = null;
}
this.current = null;
searchCount.hidden = true;
previous.disabled = true;
next.disabled = true;
}

return true;
}

protected submit() {
return true;
}

protected reset() {
const input = this.findInput('input[type="text"]')!;
const checkbox = this.findInput('input[type="checkbox"]')!;
const previous = this.find<HTMLButtonElement>(`.${cssClass('previous-result')}`)!;
const next = this.find<HTMLButtonElement>(`.${cssClass('next-result')}`)!;
const searchCount = this.find<HTMLElement>(`.${cssClass('search-count')}`)!;

this.current = null;
input.value = '';
checkbox.checked = false;
previous.disabled = true;
next.disabled = true;
searchCount.hidden = true;
}

protected cancel() {
Expand Down

0 comments on commit 7183b44

Please sign in to comment.