@koalarx/ui
Version:
Koala UI is a modern and accessible component library designed to speed up interface development in Angular projects. With simple integration and clear documentation, you can easily build robust and visually appealing applications.
642 lines (633 loc) • 35.1 kB
JavaScript
import * as i0 from '@angular/core';
import { InjectionToken, inject, Injectable, ViewContainerRef, input, booleanAttribute, linkedSignal, signal, viewChild, effect, Component, ApplicationRef, EnvironmentInjector, createComponent, inputBinding, Injector, DestroyRef, computed, isSignal, runInInjectionContext } from '@angular/core';
import { GENERIC_COMPONENT_CONTAINER_NAME } from '@koalarx/ui/core/config';
import { delay } from '@koalarx/utils/KlDelay';
import { randomString } from '@koalarx/utils/KlString';
import * as i1 from '@angular/forms';
import { ReactiveFormsModule, FormControl } from '@angular/forms';
import { KlArray } from '@koalarx/utils/KlArray';
import { Loader } from '@koalarx/ui/core/components/loader';
import { FieldErrors } from '@koalarx/ui/shared/components/field-errors';
import { InputFieldBase } from '@koalarx/ui/shared/components/input-field';
import { HookChange } from '@koalarx/ui/shared/directives';
import { isEmpty } from '@koalarx/ui/shared/utils';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { debounceTime } from 'rxjs/internal/operators/debounceTime';
const AUTOCOMPLETE_REF_TOKEN = new InjectionToken('AutocompleteRefToken');
class AutocompleteRef {
appRef = inject(AUTOCOMPLETE_APP_REF);
componentRef = inject(AUTOCOMPLETE_REF_TOKEN);
close() {
setTimeout(() => {
this.componentRef().destroy();
this.appRef.detachView(this.componentRef().hostView);
});
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: AutocompleteRef, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: AutocompleteRef });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: AutocompleteRef, decorators: [{
type: Injectable
}] });
class AutocompleteOptions {
autocompleteRef = inject(AutocompleteRef);
viewContainerRef = inject(ViewContainerRef);
firstLoad = true;
fieldId = input.required();
options = input.required();
control = input.required();
multiple = input.required();
autocompleteValue = input.required();
placeholderSearchField = input();
disableAutoTypeConversion = input(false, { transform: booleanAttribute });
list = linkedSignal(() => {
const options = this.options()();
return new KlArray(options).split(100)[0];
});
optionFocused = signal(0);
searchInputRef = viewChild('searchInput');
constructor() {
effect(() => {
const hasValue = !!this.autocompleteValue().currentValue();
if (this.firstLoad) {
this.firstLoad = false;
return;
}
if (hasValue && !this.multiple()) {
this.close();
}
});
effect(() => {
const options = this.options()();
if (options.length > 0) {
this.optionFocused.set(0);
}
});
}
updateScrollPosition(direction = 'down') {
setTimeout(() => {
const containerElement = this.viewContainerRef.element.nativeElement.querySelector('.kl-autocomplete-options-container');
const focusedOptionElement = containerElement.querySelector('label.active');
if (focusedOptionElement) {
containerElement.scrollTo({
top: direction === 'down'
? containerElement.scrollTop +
focusedOptionElement.getBoundingClientRect().height
: containerElement.scrollTop -
focusedOptionElement.getBoundingClientRect().height,
});
}
});
}
onKeyDown = (event) => {
switch (event.key) {
case 'ArrowDown': {
event.stopPropagation();
event.preventDefault();
this.optionFocused.update((value) => {
if (value < this.options()().length - 1) {
return value + 1;
}
return value;
});
this.updateScrollPosition('down');
break;
}
case 'ArrowUp': {
event.stopPropagation();
event.preventDefault();
this.optionFocused.update((value) => {
if (value > 0) {
return value - 1;
}
return value;
});
this.updateScrollPosition('up');
break;
}
}
};
onKeyup = (event) => {
const containerElement = this.viewContainerRef.element.nativeElement;
switch (event.key) {
case 'Enter': {
event.stopPropagation();
event.preventDefault();
const focusedOption = this.options()()[this.optionFocused()];
if (focusedOption) {
containerElement
.querySelectorAll('input')
.item(this.optionFocused() + 1)
.click();
}
break;
}
case 'Escape': {
event.stopPropagation();
event.preventDefault();
this.close();
break;
}
}
};
onClick = (event) => {
const containerElement = this.viewContainerRef.element.nativeElement;
const insideClick = containerElement.contains(event.target);
if (!insideClick) {
event.stopPropagation();
event.preventDefault();
this.close();
}
};
onScroll = (event) => {
const container = this.viewContainerRef.element.nativeElement.querySelector('.kl-autocomplete-options-container');
const target = event.target;
if (!container || !target.tagName)
return;
if (target !== container) {
this.close();
}
};
close() {
if (this.autocompleteValue().filterControl.value &&
!this.autocompleteValue().isOnDemand()) {
this.autocompleteValue().filterControl.reset();
}
this.autocompleteRef.close();
}
ngOnDestroy() {
document.removeEventListener('keyup', this.onKeyup);
document.removeEventListener('keydown', this.onKeyDown);
document.removeEventListener('click', this.onClick);
window.removeEventListener('scroll', this.onScroll, true);
}
ngOnInit() {
this.searchInputRef()?.nativeElement.focus();
setTimeout(() => {
document.addEventListener('keyup', this.onKeyup);
document.addEventListener('keydown', this.onKeyDown);
document.addEventListener('click', this.onClick);
window.addEventListener('scroll', this.onScroll, true);
}, 150);
}
setValue(event) {
const target = event.target;
let value = target.value;
if (!Number.isNaN(parseInt(value)) &&
!/^0+[1-9]\d*$/.test(value) &&
!this.disableAutoTypeConversion()) {
value = Number(value);
}
if (this.multiple()) {
const check = target.checked;
let currentValue = this.control().value;
if (!Array.isArray(currentValue)) {
currentValue = [currentValue];
}
if (check) {
this.control().setValue([...currentValue, value]);
}
else {
this.control().setValue(currentValue.filter((v) => v !== value));
}
}
else {
this.control().setValue(value);
}
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: AutocompleteOptions, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.6", type: AutocompleteOptions, isStandalone: true, selector: "kl-autocomplete-options", inputs: { fieldId: { classPropertyName: "fieldId", publicName: "fieldId", isSignal: true, isRequired: true, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, control: { classPropertyName: "control", publicName: "control", isSignal: true, isRequired: true, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: true, transformFunction: null }, autocompleteValue: { classPropertyName: "autocompleteValue", publicName: "autocompleteValue", isSignal: true, isRequired: true, transformFunction: null }, placeholderSearchField: { classPropertyName: "placeholderSearchField", publicName: "placeholderSearchField", isSignal: true, isRequired: false, transformFunction: null }, disableAutoTypeConversion: { classPropertyName: "disableAutoTypeConversion", publicName: "disableAutoTypeConversion", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "searchInputRef", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"bg-base-300 overflow-hidden shadow border border-neutral-200 dark:border-neutral-700 rounded-lg flex flex-col w-full\"\n [attr.aria-labelledby]=\"fieldId()\">\n\n <div class=\"kl-autocomplete-filter border-b border-neutral-200 dark:border-neutral-700\">\n <label class=\"flex items-center text-sm\">\n <i class=\"fa-solid fa-magnifying-glass opacity-60 absolute top-3 left-4\"></i>\n <input class=\"w-full p-2 pr-3 pl-10 outline-none placeholder:opacity-60\"\n #searchInput\n type=\"search\"\n [formControl]=\"autocompleteValue().filterControl\"\n placeholder=\"Type to Search...\"\n />\n </label>\n </div>\n\n <div class=\"kl-autocomplete-options-container flex flex-col overflow-auto p-1\">\n @for (item of list(); track $index) {\n @let optionLabelId = fieldId() + '-' + item.value;\n\n <label class=\"relative py-1 px-2 pr-8 rounded-md\"\n [attr.for]=\"optionLabelId\"\n [class.active]=\"$index === optionFocused()\"\n (mouseover)=\"optionFocused.set($index)\">\n\n <input\n [type]=\"multiple() ? 'checkbox' : 'radio'\"\n [id]=\"optionLabelId\"\n [name]=\"fieldId()\"\n [attr.value]=\"item.value\"\n [checked]=\"multiple()\n ? control().value?.includes(item.value)\n : item.value === control().value\"\n (change)=\"setValue($event)\"\n />\n {{item.label}}\n </label>\n }\n </div>\n</div>\n", dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: AutocompleteOptions, decorators: [{
type: Component,
args: [{ selector: 'kl-autocomplete-options', imports: [ReactiveFormsModule], template: "<div class=\"bg-base-300 overflow-hidden shadow border border-neutral-200 dark:border-neutral-700 rounded-lg flex flex-col w-full\"\n [attr.aria-labelledby]=\"fieldId()\">\n\n <div class=\"kl-autocomplete-filter border-b border-neutral-200 dark:border-neutral-700\">\n <label class=\"flex items-center text-sm\">\n <i class=\"fa-solid fa-magnifying-glass opacity-60 absolute top-3 left-4\"></i>\n <input class=\"w-full p-2 pr-3 pl-10 outline-none placeholder:opacity-60\"\n #searchInput\n type=\"search\"\n [formControl]=\"autocompleteValue().filterControl\"\n placeholder=\"Type to Search...\"\n />\n </label>\n </div>\n\n <div class=\"kl-autocomplete-options-container flex flex-col overflow-auto p-1\">\n @for (item of list(); track $index) {\n @let optionLabelId = fieldId() + '-' + item.value;\n\n <label class=\"relative py-1 px-2 pr-8 rounded-md\"\n [attr.for]=\"optionLabelId\"\n [class.active]=\"$index === optionFocused()\"\n (mouseover)=\"optionFocused.set($index)\">\n\n <input\n [type]=\"multiple() ? 'checkbox' : 'radio'\"\n [id]=\"optionLabelId\"\n [name]=\"fieldId()\"\n [attr.value]=\"item.value\"\n [checked]=\"multiple()\n ? control().value?.includes(item.value)\n : item.value === control().value\"\n (change)=\"setValue($event)\"\n />\n {{item.label}}\n </label>\n }\n </div>\n</div>\n" }]
}], ctorParameters: () => [] });
const AUTOCOMPLETE_APP_REF = new InjectionToken('AutocompleteAppRef');
class Autocomplete {
appRef = inject(ApplicationRef);
viewContainerRef = inject(ViewContainerRef);
injector = inject(EnvironmentInjector);
generateElementId() {
let elementId;
do {
elementId = randomString(50, {
numbers: false,
lowercase: true,
uppercase: true,
specialCharacters: false,
});
} while (document.getElementById(elementId));
return elementId;
}
calculatePosition(container) {
const autocompleteField = this.viewContainerRef.element
.nativeElement;
const currentTop = window.scrollY;
const position = autocompleteField.getBoundingClientRect();
const optionsContainer = container.querySelector('.kl-autocomplete-options-container');
const filterContainer = container?.querySelector('.kl-autocomplete-filter');
const filterContainerHeight = (filterContainer?.clientHeight || 0) + 2;
if (position) {
const screenHeight = document.body.clientHeight;
const maxHeight = (screenHeight * 40) / 100;
let top = position.bottom;
let height = Math.abs(screenHeight - top);
if (height > maxHeight) {
height = maxHeight;
}
const percentFillOnScreen = (height * 100) / screenHeight;
if (percentFillOnScreen <= 20) {
const optionsHeight = optionsContainer?.scrollHeight || 0;
const currentHeight = optionsHeight + filterContainerHeight;
if (optionsHeight > 0 && currentHeight <= maxHeight) {
height = currentHeight;
}
else {
height = Math.abs(screenHeight - (screenHeight - position.top));
if (height > maxHeight) {
height = maxHeight;
}
}
top = position.top - height;
}
top += currentTop;
container.style.top = `${top}px`;
container.style.maxHeight = `${height}px`;
return { top, left: position.left, width: position.width, height };
}
return null;
}
async waitForButtonEnabled(buttonElement, timeout) {
const delayTime = 50;
let ellapsedTime = 0;
while (buttonElement.disabled && ellapsedTime <= timeout) {
await delay(delayTime);
ellapsedTime += delayTime;
}
}
async positionOnScreen(container) {
const autocompleteField = this.viewContainerRef.element
.nativeElement;
const autocompleteFieldButton = autocompleteField.querySelector('button');
if (autocompleteFieldButton) {
await this.waitForButtonEnabled(autocompleteFieldButton, 5000);
}
const position = this.calculatePosition(container);
if (position) {
const { left, width } = position;
container.style.position = 'absolute';
container.style.display = 'flex';
container.style.left = `${left}px`;
container.style.width = `${width}px`;
container.style.height = `auto`;
container.style.zIndex = '99';
container.style.overflow = 'hidden';
container.style.transition = 'all 0.1s ease-in-out';
const selectedOptions = autocompleteField.querySelector('.selected-options');
if (selectedOptions) {
selectedOptions.onchange = () => this.positionOnScreen(container);
}
}
}
async open(data) {
const main = document.querySelector(GENERIC_COMPONENT_CONTAINER_NAME);
if (main) {
const elementId = this.generateElementId();
const container = main.appendChild(document.createElement('div'));
container.id = elementId;
await this.positionOnScreen(container);
const componentRef = createComponent(AutocompleteOptions, {
environmentInjector: this.injector,
hostElement: container,
elementInjector: Injector.create({
providers: [
{ provide: AUTOCOMPLETE_APP_REF, useValue: this.appRef },
{
provide: AUTOCOMPLETE_REF_TOKEN,
useValue: () => componentRef,
},
{
provide: AutocompleteRef,
deps: [AUTOCOMPLETE_APP_REF, AUTOCOMPLETE_REF_TOKEN],
},
],
}),
bindings: [
...Object.keys(data).map((key) => inputBinding(key, () => data[key])),
],
});
this.appRef.attachView(componentRef.hostView);
componentRef.changeDetectorRef.detectChanges();
this.calculatePosition(container);
}
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: Autocomplete, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: Autocomplete });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: Autocomplete, decorators: [{
type: Injectable
}] });
class AutocompleteBuilder {
onDemand(config) {
return config;
}
onServer(config) {
return config;
}
inMemory(config) {
return config;
}
inMemoryWithLoading(config) {
return config;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: AutocompleteBuilder, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: AutocompleteBuilder, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: AutocompleteBuilder, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}] });
class AutocompleteValue {
destroyRef = inject(DestroyRef);
_filter = signal('');
_control;
_currentValue = signal(null);
_multiple = false;
_options;
_autofill = signal(null);
_isLoading;
_internalFilter = signal(null);
_isOnDemand;
_requestOptionsParams = signal({
filter: null,
autofill: null,
});
_selectedOption = computed(() => {
const currentValue = this._currentValue();
if (Array.isArray(currentValue)) {
return null;
}
return currentValue;
});
_selectedOptions = computed(() => {
const currentValue = this._currentValue();
if (Array.isArray(currentValue)) {
return currentValue;
}
return [];
});
_hasValue = computed(() => {
const currentValue = this._currentValue();
const value = Array.isArray(currentValue)
? currentValue
: currentValue?.value;
if (Array.isArray(value)) {
return value.length > 0;
}
else if (typeof value === 'number') {
return value >= 0;
}
return !!value;
});
filterControl = new FormControl('');
get filter() {
return this._filter.asReadonly();
}
get hasValue() {
return this._hasValue;
}
get currentValue() {
return this._currentValue.asReadonly();
}
get selectedOption() {
return this._selectedOption;
}
get selectedOptions() {
return this._selectedOptions;
}
get autofill() {
return this._autofill.asReadonly();
}
get requestOptionsParams() {
return this._requestOptionsParams.asReadonly();
}
set internalFilter(value) {
this._internalFilter.set(value);
this._requestOptionsParams.update(() => ({
filter: this.filterControl.enabled ? null : this.filterControl.value,
internalFilter: this._internalFilter(),
autofill: null,
}));
}
get isOnDemand() {
if (!this._isOnDemand) {
return signal(false);
}
return this._isOnDemand.asReadonly();
}
selectedOptionIsDiff(options) {
if (this._multiple) {
return (!Array.isArray(options) ||
options.length !== this._selectedOptions().length ||
options.some((opt, index) => opt.value !== this._selectedOptions()[index].value));
}
return (!options ||
options.value !== this._selectedOption()?.value);
}
async selectOption(value) {
while (this._isLoading()) {
await delay(100);
}
if (!this._options) {
return;
}
const options = this._multiple
? this._options()?.filter((opt) => `${value}`?.includes(`${opt.value}`))
: this._options()?.find((opt) => `${opt.value}` === `${value}`);
if (!isEmpty(value) && !options && this._isOnDemand()) {
this._requestOptionsParams.update(() => ({
internalFilter: this._internalFilter(),
autofill: value,
}));
await delay(100);
this.selectOption(value);
return;
}
if (options && this.selectedOptionIsDiff(options)) {
this._currentValue.update(() => {
if (this._multiple) {
if (Array.isArray(options)) {
return options;
}
return [options];
}
return options;
});
}
}
async makeAutofill() {
if (!isEmpty(this._control?.value)) {
while (this._isLoading()) {
await delay(100);
}
this.selectOption(this._control?.value);
const currentValue = this._currentValue();
if ((this._multiple &&
((Array.isArray(currentValue) && currentValue.length === 0) ||
!Array.isArray(currentValue))) ||
(!this._multiple && isEmpty(currentValue))) {
this._autofill.set(this._control?.value);
this._requestOptionsParams.update((params) => ({
...params,
internalFilter: this._internalFilter(),
autofill: this._autofill(),
}));
}
}
}
init(control, options, isLoading, isOnDemand, multiple = false) {
this._control = control;
this._options = options;
this._multiple = multiple;
this._isLoading = isLoading;
this._isOnDemand = isOnDemand;
this.filterControl.valueChanges
.pipe(takeUntilDestroyed(this.destroyRef), debounceTime(500))
.subscribe((value) => {
this._autofill.set(null);
this._filter.set(value);
this._requestOptionsParams.update(() => ({
filter: value,
internalFilter: this._internalFilter(),
autofill: null,
}));
});
this._control.valueChanges
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((value) => this.selectOption(value));
}
clear(event) {
event.preventDefault();
this._control?.setValue(null);
this._currentValue.set(null);
this._requestOptionsParams.update(() => ({
filter: null,
internalFilter: this._internalFilter(),
autofill: null,
}));
}
remove(event, value) {
event.preventDefault();
if (!this._multiple) {
return;
}
if (!this._control) {
return;
}
const currentValue = this._control.value;
this._control?.setValue(currentValue.filter((v) => v !== value));
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: AutocompleteValue, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: AutocompleteValue });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: AutocompleteValue, decorators: [{
type: Injectable
}] });
class AutocompleteField extends InputFieldBase {
injector = inject(Injector);
autocompleteValue = inject(AutocompleteValue);
autocomplete = inject(Autocomplete);
options = input.required();
multiple = input(false, { transform: booleanAttribute });
placeholderSearchField = input();
disableAutoTypeConversion = input(false, {
transform: booleanAttribute,
});
isLoading = signal(false);
optionList = signal([]);
optionsResource = signal(null);
isOnDemand = linkedSignal(() => this.optionsResource()?.onDemand !== undefined);
constructor() {
super();
effect(() => {
const optionList = this.optionList();
const autofill = this.autocompleteValue.autofill();
if (optionList.length > 0 && !isEmpty(autofill)) {
this.autocompleteValue.makeAutofill();
}
});
effect(() => {
const options = this.optionsResource();
if (!options) {
return;
}
const { onDemand, onServer, inMemory, inMemoryWithLoading } = options;
if (onDemand) {
this.optionList.set(onDemand.value());
this.isLoading.set(onDemand.isLoading());
}
else if (onServer) {
this.optionList.set(this.applyFilter(onServer.value()));
this.isLoading.set(onServer.isLoading());
}
else if (inMemory) {
this.optionList.set(this.applyFilter(inMemory));
this.isLoading.set(false);
}
else if (inMemoryWithLoading) {
const optionsWithLoading = inMemoryWithLoading();
this.optionList.set(this.applyFilter(optionsWithLoading ?? []));
this.isLoading.set(!optionsWithLoading);
}
});
}
applyFilter(options) {
const filter = this.autocompleteValue.filter() ?? '';
return options.filter((option) => option.label.toLowerCase().includes(filter.toLowerCase()));
}
generateOptionsResource() {
const options = this.options();
if (Object.hasOwn(options, 'value')) {
return { onServer: options };
}
else if (isSignal(options)) {
return {
inMemoryWithLoading: options,
};
}
else if (typeof options === 'function') {
const resourceFnOptions = options;
return {
onDemand: runInInjectionContext(this.injector, () => resourceFnOptions(this.autocompleteValue.requestOptionsParams)),
};
}
return { inMemory: options };
}
open() {
this.autocomplete.open({
fieldId: this.fieldId,
options: this.optionList,
control: this.control(),
multiple: this.multiple(),
autocompleteValue: this.autocompleteValue,
placeholderSearchField: this.placeholderSearchField(),
disableAutoTypeConversion: this.disableAutoTypeConversion(),
});
}
ngOnInit() {
this.autocompleteValue.init(this.control(), this.optionList, this.isLoading, this.isOnDemand, this.multiple());
this.optionsResource.set(this.generateOptionsResource());
this.autocompleteValue.makeAutofill();
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: AutocompleteField, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.6", type: AutocompleteField, isStandalone: true, selector: "kl-autocomplete-field", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, placeholderSearchField: { classPropertyName: "placeholderSearchField", publicName: "placeholderSearchField", isSignal: true, isRequired: false, transformFunction: null }, disableAutoTypeConversion: { classPropertyName: "disableAutoTypeConversion", publicName: "disableAutoTypeConversion", isSignal: true, isRequired: false, transformFunction: null } }, providers: [Autocomplete, AutocompleteValue], usesInheritance: true, ngImport: i0, template: "<button class=\"relative select hover:cursor-default bg-base-100 w-full min-h-10 h-full flex items-center justify-between flex-nowrap\"\n [class.has-value]=\"autocompleteValue.hasValue()\"\n type=\"button\"\n tabindex=\"0\"\n [id]=\"fieldId\"\n [disabled]=\"isDisabled() || isLoading()\"\n (focus)=\"control().markAsTouched()\"\n (click)=\"open()\">\n\n @if (label(); as label) {\n <span class=\"autocomplete-label flex items-center gap-2 whitespace-nowrap text-neutral-900 dark:text-neutral-300\">\n <span>{{label}} {{ isRequired() ? '*' : '' }}</span>\n </span>\n }\n\n <div class=\"flex flex-wrap gap-1 pt-3 pb-2 items-center w-full h-full selected-options\"\n [hookChange]=\"!isLoading() && (autocompleteValue.filter() || autocompleteValue.currentValue())\"\n [class.hidden]=\"!autocompleteValue.hasValue()\">\n @if (multiple()) {\n @for (item of autocompleteValue.selectedOptions(); track $index) {\n <span class=\"flex items-center badge badge-primary badge-sm rounded-sm\">\n <span>{{item.label}}</span>\n <i class=\"fa-solid fa-xmark text-neutral-400 hover:cursor-pointer\"\n (click)=\"autocompleteValue.remove($event, item.value)\">\n </i>\n </span>\n }\n } @else {\n {{autocompleteValue.selectedOption()?.label}}\n }\n </div>\n\n @if (isLoading()) {\n <kl-loader size=\"small\" />\n } @else if (autocompleteValue.hasValue()) {\n <i class=\"fa-solid fa-xmark text-neutral-500 hover:cursor-pointer pt-1 pr-2\"\n (click)=\"autocompleteValue.clear($event)\">\n </i>\n }\n</button>\n\n<kl-field-errors [field]=\"control()\">\n <ng-container errors>\n <ng-content select=\"[errors]\" />\n </ng-container>\n</kl-field-errors>\n", dependencies: [{ kind: "component", type: Loader, selector: "kl-loader", inputs: ["size"] }, { kind: "component", type: FieldErrors, selector: "kl-field-errors", inputs: ["field"] }, { kind: "directive", type: HookChange, selector: "[hookChange]", inputs: ["hookChange"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: AutocompleteField, decorators: [{
type: Component,
args: [{ selector: 'kl-autocomplete-field', providers: [Autocomplete, AutocompleteValue], imports: [Loader, FieldErrors, HookChange], template: "<button class=\"relative select hover:cursor-default bg-base-100 w-full min-h-10 h-full flex items-center justify-between flex-nowrap\"\n [class.has-value]=\"autocompleteValue.hasValue()\"\n type=\"button\"\n tabindex=\"0\"\n [id]=\"fieldId\"\n [disabled]=\"isDisabled() || isLoading()\"\n (focus)=\"control().markAsTouched()\"\n (click)=\"open()\">\n\n @if (label(); as label) {\n <span class=\"autocomplete-label flex items-center gap-2 whitespace-nowrap text-neutral-900 dark:text-neutral-300\">\n <span>{{label}} {{ isRequired() ? '*' : '' }}</span>\n </span>\n }\n\n <div class=\"flex flex-wrap gap-1 pt-3 pb-2 items-center w-full h-full selected-options\"\n [hookChange]=\"!isLoading() && (autocompleteValue.filter() || autocompleteValue.currentValue())\"\n [class.hidden]=\"!autocompleteValue.hasValue()\">\n @if (multiple()) {\n @for (item of autocompleteValue.selectedOptions(); track $index) {\n <span class=\"flex items-center badge badge-primary badge-sm rounded-sm\">\n <span>{{item.label}}</span>\n <i class=\"fa-solid fa-xmark text-neutral-400 hover:cursor-pointer\"\n (click)=\"autocompleteValue.remove($event, item.value)\">\n </i>\n </span>\n }\n } @else {\n {{autocompleteValue.selectedOption()?.label}}\n }\n </div>\n\n @if (isLoading()) {\n <kl-loader size=\"small\" />\n } @else if (autocompleteValue.hasValue()) {\n <i class=\"fa-solid fa-xmark text-neutral-500 hover:cursor-pointer pt-1 pr-2\"\n (click)=\"autocompleteValue.clear($event)\">\n </i>\n }\n</button>\n\n<kl-field-errors [field]=\"control()\">\n <ng-container errors>\n <ng-content select=\"[errors]\" />\n </ng-container>\n</kl-field-errors>\n" }]
}], ctorParameters: () => [] });
/**
* Generated bundle index. Do not edit.
*/
export { AUTOCOMPLETE_APP_REF, AUTOCOMPLETE_REF_TOKEN, Autocomplete, AutocompleteBuilder, AutocompleteField, AutocompleteOptions, AutocompleteRef, AutocompleteValue };
//# sourceMappingURL=koalarx-ui-shared-components-input-field-autocomplete.mjs.map