Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions projects/element-ng/typeahead/si-typeahead.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@
<ul
#typeahead
class="typeahead dropdown-menu"
[class.empty-loading]="parent.typeaheadLoading() && matches().length === 0"
[siLoading]="parent.typeaheadLoading() && matches().length === 0"
[class.empty-loading]="parent.loading() && matches().length === 0"
[siLoading]="parent.loading() && matches().length === 0"
[initialDelay]="false"
[siAutocompleteListboxFor]="autocompleteDirective"
[siAutocompleteDefaultIndex]="parent.typeaheadAutoSelectIndex()"
[attr.aria-label]="parent.typeaheadAutocompleteListLabel() | translate"
(siAutocompleteOptionSubmitted)="selectMatch($event)"
>
@if (parent.typeaheadLoading() && matches().length) {
@if (parent.loading() && matches().length) {
<li class="dropdown-item" [siLoading]="true" [initialDelay]="false"></li>
}
<!-- Loop through every match and bind events, the mousedown prevent default is to prevent the host from losing focus on click -->
Expand Down
42 changes: 25 additions & 17 deletions projects/element-ng/typeahead/si-typeahead.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,17 @@ export class SiTypeaheadDirective implements OnChanges, OnDestroy {
* @experimental
*/
readonly typeaheadCreateOption = input<TranslatableString>();

/**
* Allows to externally control the loading state of the typeahead.
* When set to `true`, the typeahead will show a loading spinner.
* Merges with the internal {@link optionSourceLoading} state via the {@link loading} computed signal.
*
* @experimental
* @defaultValue false
*/
readonly typeaheadLoading = input(false, { transform: booleanAttribute });

/**
* Emits an Event when the input field is changed.
*/
Expand Down Expand Up @@ -269,6 +280,14 @@ export class SiTypeaheadDirective implements OnChanges, OnDestroy {
/** @internal */
readonly query = signal('');

/**
* Combined loading state that is `true` when either {@link typeaheadLoading} input is `true`
* or the async option source ({@link optionSourceLoading}) is currently fetching.
*
* @internal
*/
readonly loading = computed(() => this.optionSourceLoading() || this.typeaheadLoading());

/**
* Indicates whether the typeahead is shown.
*/
Expand All @@ -290,16 +309,6 @@ export class SiTypeaheadDirective implements OnChanges, OnDestroy {

private loadingSubscription?: Subscription;

/**
* Indicates whether the typeahead is currently loading.
* When using {@link TypeaheadOptionSource}, this is controlled by the `isLoading()` method
* or automatically set to `true` while fetching options.
*
* @internal
* @defaultValue false
*/
readonly typeaheadLoading = signal(false);

/**
* Indicates that the typeahead can be potentially open.
* This signal is typically `true` when the input is focussed.
Expand All @@ -308,6 +317,7 @@ export class SiTypeaheadDirective implements OnChanges, OnDestroy {
*/
private readonly canBeOpen = signal(false);
private readonly selectionCounter = signal(0);
private readonly optionSourceLoading = signal(false);
private readonly typeaheadOptions = toSignal(
this.$typeahead.pipe(
map(options =>
Expand Down Expand Up @@ -380,9 +390,7 @@ export class SiTypeaheadDirective implements OnChanges, OnDestroy {
this.canBeOpen() &&
this.query().length >= this.typeaheadMinLength() &&
// Ensure that we have content to show: matches, create option or loading indicator
(matches.length ||
(this.typeaheadCreateOption() && this.query().length) ||
this.typeaheadLoading())
(matches.length || (this.typeaheadCreateOption() && this.query().length) || this.loading())
) {
const escapedQuery = this.escapeRegex(this.query());
const equalsExp = new RegExp(`^${escapedQuery}$`, 'i');
Expand All @@ -409,11 +417,11 @@ export class SiTypeaheadDirective implements OnChanges, OnDestroy {
if (Array.isArray(typeahead)) {
// Handle TypeaheadArray
this.$typeahead.next(typeahead);
this.typeaheadLoading.set(false);
this.optionSourceLoading.set(false);
} else if (isObservable(typeahead)) {
// Handle TypeaheadObservable
this.sourceSubscription = typeahead.subscribe(this.$typeahead);
this.typeaheadLoading.set(false);
this.optionSourceLoading.set(false);
} else if (typeof typeahead === 'function') {
// Handle TypeaheadOptionSource
this.handleTypeaheadOptionSource(typeahead);
Expand Down Expand Up @@ -484,13 +492,13 @@ export class SiTypeaheadDirective implements OnChanges, OnDestroy {
this.sourceSubscription.add(
queryObservable
.pipe(switchMap(() => timer(500).pipe(takeUntil(resultObservable))))
.subscribe(() => this.typeaheadLoading.set(true))
.subscribe(() => this.optionSourceLoading.set(true))
);

this.sourceSubscription.add(
resultObservable.subscribe(result => {
this.$typeahead.next(result);
this.typeaheadLoading.set(false);
this.optionSourceLoading.set(false);
})
);
}
Expand Down
Loading