diff --git a/projects/aca-content/assets/i18n/en.json b/projects/aca-content/assets/i18n/en.json
index 8f1445966..12f368ab4 100644
--- a/projects/aca-content/assets/i18n/en.json
+++ b/projects/aca-content/assets/i18n/en.json
@@ -574,7 +574,8 @@
"FILES": "Files",
"FOLDERS": "Folders",
"LIBRARIES": "Libraries",
- "HINT": "Search input must have at least 2 alphanumeric characters."
+ "HINT": "Search input must have at least 2 alphanumeric characters.",
+ "REQUIRED": "Search term is required."
},
"SORT": {
"SORTING_OPTION": "Sort by",
diff --git a/projects/aca-content/src/lib/components/search/search-input-control/search-input-control.component.html b/projects/aca-content/src/lib/components/search/search-input-control/search-input-control.component.html
index 609f50d88..acde13ebd 100644
--- a/projects/aca-content/src/lib/components/search/search-input-control/search-input-control.component.html
+++ b/projects/aca-content/src/lib/components/search/search-input-control/search-input-control.component.html
@@ -5,7 +5,8 @@
mat-icon-button
matPrefix
class="app-search-button"
- (click)="searchSubmit()"
+ (click)="openDropdown()"
+ (keydown)="onSearchButtonKeyDown($event)"
[title]="'SEARCH.BUTTON.TOOLTIP' | translate"
>
search
@@ -18,7 +19,9 @@
[type]="inputType"
id="app-control-input"
[formControl]="searchFieldFormControl"
- (keyup.enter)="searchSubmit()"
+ (keydown)="onInputKeyDown($event)"
+ (focus)="onInputFocus()"
+ (blur)="onBlur()"
[placeholder]="'SEARCH.INPUT.PLACEHOLDER' | translate"
autocomplete="off"
/>
diff --git a/projects/aca-content/src/lib/components/search/search-input-control/search-input-control.component.spec.ts b/projects/aca-content/src/lib/components/search/search-input-control/search-input-control.component.spec.ts
index ea035e75b..055f50a48 100644
--- a/projects/aca-content/src/lib/components/search/search-input-control/search-input-control.component.spec.ts
+++ b/projects/aca-content/src/lib/components/search/search-input-control/search-input-control.component.spec.ts
@@ -42,14 +42,24 @@ describe('SearchInputControlComponent', () => {
fixture.detectChanges();
});
- it('should emit submit event on searchSubmit', () => {
- component.searchTerm = 'mock-search-term';
+ it('should emit submit event if form is valid', () => {
+ component.searchTerm = 'valid';
- let submittedSearchTerm = '';
- component.submit.subscribe((searchTerm) => (submittedSearchTerm = searchTerm));
+ let submittedTerm = '';
+ component.submit.subscribe((term) => (submittedTerm = term));
component.searchSubmit();
- expect(submittedSearchTerm).toBe('mock-search-term');
+ expect(submittedTerm).toBe('valid');
+ });
+
+ it('should not emit submit event if form is invalid', () => {
+ component.searchTerm = '';
+
+ let submitted = false;
+ component.submit.subscribe(() => (submitted = true));
+
+ component.searchSubmit();
+ expect(submitted).toBeFalse();
});
it('should emit searchChange event on inputChange', () => {
@@ -87,4 +97,57 @@ describe('SearchInputControlComponent', () => {
fixture.detectChanges();
expect(component.isTermTooShort()).toBe(false);
});
+
+ it('should activate search bar on openDropdown()', () => {
+ component.openDropdown();
+ expect(component.isSearchBarActive).toBeTrue();
+ });
+
+ it('should set isSearchBarActive to true on input focus', () => {
+ component.onInputFocus();
+ expect(component.isSearchBarActive).toBeTrue();
+ });
+
+ it('should reset isSearchBarActive and mark field as untouched on blur', () => {
+ component.onInputFocus();
+ component.searchFieldFormControl.markAsTouched();
+
+ component.onBlur();
+
+ expect(component.isSearchBarActive).toBeFalse();
+ expect(component.searchFieldFormControl.touched).toBeFalse();
+ });
+
+ it('should handle Enter key on input and submit if valid', () => {
+ const event = new KeyboardEvent('keydown', { key: 'Enter' });
+ spyOn(event, 'preventDefault');
+ spyOn(event, 'stopPropagation');
+ component.searchTerm = 'abc';
+
+ let emitted = '';
+ component.submit.subscribe((val) => (emitted = val));
+
+ component.onInputKeyDown(event);
+ expect(event.preventDefault).toHaveBeenCalled();
+ expect(event.stopPropagation).toHaveBeenCalled();
+ expect(emitted).toBe('abc');
+ });
+
+ it('should prevent default and open dropdown on Enter key from search icon', () => {
+ const event = new KeyboardEvent('keydown', { key: 'Enter' });
+ spyOn(event, 'preventDefault');
+
+ component.onSearchButtonKeyDown(event);
+ expect(event.preventDefault).toHaveBeenCalled();
+ expect(component.isSearchBarActive).toBeTrue();
+ });
+
+ it('should not submit on non-Enter key press', () => {
+ const event = new KeyboardEvent('keydown', { key: 'Escape' });
+ let emitted = false;
+ component.submit.subscribe(() => (emitted = true));
+
+ component.onInputKeyDown(event);
+ expect(emitted).toBeFalse();
+ });
});
diff --git a/projects/aca-content/src/lib/components/search/search-input-control/search-input-control.component.ts b/projects/aca-content/src/lib/components/search/search-input-control/search-input-control.component.ts
index 6b73d6052..c06700d51 100644
--- a/projects/aca-content/src/lib/components/search/search-input-control/search-input-control.component.ts
+++ b/projects/aca-content/src/lib/components/search/search-input-control/search-input-control.component.ts
@@ -29,7 +29,7 @@ import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
-import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { FormControl, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({
@@ -65,7 +65,9 @@ export class SearchInputControlComponent implements OnInit {
@ViewChild('searchInput', { static: true })
searchInput: ElementRef;
- searchFieldFormControl = new FormControl('');
+ searchFieldFormControl = new FormControl('', [Validators.required]);
+
+ isSearchBarActive = false;
get searchTerm(): string {
return this.searchFieldFormControl.value.replace('text:', 'TEXT:');
@@ -82,8 +84,36 @@ export class SearchInputControlComponent implements OnInit {
});
}
+ onSearchButtonKeyDown(event: KeyboardEvent): void {
+ if (event.key === 'Enter') {
+ event.preventDefault();
+ this.openDropdown();
+ }
+ }
+
+ onInputKeyDown(event: KeyboardEvent): void {
+ if (event.key === 'Enter') {
+ event.preventDefault();
+ event.stopPropagation();
+ this.searchSubmit();
+ }
+ }
+
+ openDropdown() {
+ this.isSearchBarActive = true;
+ setTimeout(() => {
+ this.searchInput.nativeElement.focus();
+ }, 0);
+ }
+
+ onInputFocus() {
+ this.isSearchBarActive = true;
+ }
+
searchSubmit() {
- if (!this.searchFieldFormControl.errors) {
+ this.searchFieldFormControl.markAsTouched();
+
+ if (this.searchFieldFormControl.valid) {
this.submit.emit(this.searchTerm);
}
}
@@ -93,6 +123,11 @@ export class SearchInputControlComponent implements OnInit {
this.searchChange.emit('');
}
+ onBlur() {
+ this.isSearchBarActive = false;
+ this.searchFieldFormControl.markAsUntouched();
+ }
+
isTermTooShort() {
return !!(this.searchTerm && this.searchTerm.length < 2);
}
diff --git a/projects/aca-content/src/lib/components/search/search-input/search-input.component.html b/projects/aca-content/src/lib/components/search/search-input/search-input.component.html
index e5a2ef805..5f647124f 100644
--- a/projects/aca-content/src/lib/components/search/search-input/search-input.component.html
+++ b/projects/aca-content/src/lib/components/search/search-input/search-input.component.html
@@ -36,7 +36,14 @@
(submit)="onSearchSubmit($event)"
(searchChange)="onSearchChange($event)"
/>
- {{ 'SEARCH.INPUT.HINT' | translate }}
+
+
+ {{ 'SEARCH.INPUT.HINT' | translate }}
+
+
+ {{ 'SEARCH.INPUT.REQUIRED' | translate }}
+
+