@neuravision/ng-construct 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -7790,6 +7790,681 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
|
|
|
7790
7790
|
`, styles: [":host{display:block}\n"] }]
|
|
7791
7791
|
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], compareWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "compareWith", required: false }] }], comboboxId: [{ type: i0.Input, args: [{ isSignal: true, alias: "comboboxId", required: false }] }], inputRef: [{ type: i0.ViewChild, args: ['inputEl', { isSignal: true }] }], listboxRef: [{ type: i0.ViewChild, args: ['listboxEl', { isSignal: true }] }] } });
|
|
7792
7792
|
|
|
7793
|
+
/**
|
|
7794
|
+
* Marks an `<ng-template>` as the custom row renderer for
|
|
7795
|
+
* {@link AfAutocompleteComponent}. Without it the component falls back to
|
|
7796
|
+
* rendering `label` + `description`.
|
|
7797
|
+
*
|
|
7798
|
+
* @example
|
|
7799
|
+
* <af-autocomplete [options]="opts">
|
|
7800
|
+
* <ng-template afAutocompleteOption let-option="option">
|
|
7801
|
+
* <af-avatar [name]="option.label" />
|
|
7802
|
+
* {{ option.label }}
|
|
7803
|
+
* </ng-template>
|
|
7804
|
+
* </af-autocomplete>
|
|
7805
|
+
*/
|
|
7806
|
+
class AfAutocompleteOptionDirective {
|
|
7807
|
+
template = inject(TemplateRef);
|
|
7808
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: AfAutocompleteOptionDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
7809
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.2", type: AfAutocompleteOptionDirective, isStandalone: true, selector: "ng-template[afAutocompleteOption]", ngImport: i0 });
|
|
7810
|
+
}
|
|
7811
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: AfAutocompleteOptionDirective, decorators: [{
|
|
7812
|
+
type: Directive,
|
|
7813
|
+
args: [{ selector: 'ng-template[afAutocompleteOption]' }]
|
|
7814
|
+
}] });
|
|
7815
|
+
/**
|
|
7816
|
+
* Accessible async / remote autocomplete (typeahead) following the WAI-ARIA 1.2
|
|
7817
|
+
* combobox-with-listbox pattern.
|
|
7818
|
+
*
|
|
7819
|
+
* Unlike {@link AfComboboxComponent} — which owns a static option list and
|
|
7820
|
+
* filters it by label substring — this component is **external-filter only**:
|
|
7821
|
+
* the consumer owns fetching and filtering, then feeds the resolved results in
|
|
7822
|
+
* via `options` (and toggles `loading`). That makes it suitable for remote
|
|
7823
|
+
* search across multiple sources, where a hit may have matched on a field that
|
|
7824
|
+
* is not part of its visible label (e.g. a user matched by e-mail).
|
|
7825
|
+
*
|
|
7826
|
+
* It emits a selection **event** rather than binding a value, so the same box
|
|
7827
|
+
* can drive an action (apply a filter, navigate) while keeping the free text.
|
|
7828
|
+
*
|
|
7829
|
+
* Features: option groups with headings, a loading row, an empty row, rich
|
|
7830
|
+
* rows via {@link AfAutocompleteOptionDirective}, full keyboard support
|
|
7831
|
+
* (Arrow/Home/End/Enter/Escape/Tab) and screen-reader announcements.
|
|
7832
|
+
*
|
|
7833
|
+
* @example
|
|
7834
|
+
* <af-autocomplete
|
|
7835
|
+
* label="Search"
|
|
7836
|
+
* [(query)]="query"
|
|
7837
|
+
* [options]="results()"
|
|
7838
|
+
* [loading]="loading()"
|
|
7839
|
+
* [minChars]="2"
|
|
7840
|
+
* clearQueryOnSelect
|
|
7841
|
+
* (optionSelected)="apply($event)"
|
|
7842
|
+
* >
|
|
7843
|
+
* <ng-template afAutocompleteOption let-option="option">…</ng-template>
|
|
7844
|
+
* </af-autocomplete>
|
|
7845
|
+
*/
|
|
7846
|
+
class AfAutocompleteComponent {
|
|
7847
|
+
static nextId = 0;
|
|
7848
|
+
/** Field label rendered above the input. */
|
|
7849
|
+
label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
|
|
7850
|
+
/** Placeholder text for the input. */
|
|
7851
|
+
placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
|
|
7852
|
+
/** Hint text shown below the input (hidden while {@link error} is set). */
|
|
7853
|
+
hint = input('', ...(ngDevMode ? [{ debugName: "hint" }] : []));
|
|
7854
|
+
/** Error message — shows the error state and message. */
|
|
7855
|
+
error = input('', ...(ngDevMode ? [{ debugName: "error" }] : []));
|
|
7856
|
+
/** Whether the field is required (renders the `*` indicator). */
|
|
7857
|
+
required = input(false, { ...(ngDevMode ? { debugName: "required" } : {}), transform: booleanAttribute });
|
|
7858
|
+
/** Whether the input is disabled. */
|
|
7859
|
+
disabled = model(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
7860
|
+
/** Size variant. */
|
|
7861
|
+
size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
|
|
7862
|
+
/**
|
|
7863
|
+
* The suggestions to display, already filtered and ordered by the consumer.
|
|
7864
|
+
* The component does not filter them.
|
|
7865
|
+
*/
|
|
7866
|
+
options = input([], ...(ngDevMode ? [{ debugName: "options" }] : []));
|
|
7867
|
+
/** Shows the loading row / spinner while a fetch is in flight. */
|
|
7868
|
+
loading = input(false, { ...(ngDevMode ? { debugName: "loading" } : {}), transform: booleanAttribute });
|
|
7869
|
+
/** Minimum trimmed query length before the listbox opens. */
|
|
7870
|
+
minChars = input(1, ...(ngDevMode ? [{ debugName: "minChars" }] : []));
|
|
7871
|
+
/** Text shown when a query is present but no options match. */
|
|
7872
|
+
emptyText = input('No results found', ...(ngDevMode ? [{ debugName: "emptyText" }] : []));
|
|
7873
|
+
/** Text shown in the loading row. */
|
|
7874
|
+
loadingText = input('Searching…', ...(ngDevMode ? [{ debugName: "loadingText" }] : []));
|
|
7875
|
+
/** Whether re-focusing the input re-opens the listbox. */
|
|
7876
|
+
openOnFocus = input(true, { ...(ngDevMode ? { debugName: "openOnFocus" } : {}), transform: booleanAttribute });
|
|
7877
|
+
/**
|
|
7878
|
+
* When true the panel stays closed while there are no options and no fetch
|
|
7879
|
+
* is in flight — instead of showing the empty row. Useful when the same box
|
|
7880
|
+
* also drives a side effect (e.g. a live text filter) so an empty suggestion
|
|
7881
|
+
* list should be silent rather than intrusive.
|
|
7882
|
+
*/
|
|
7883
|
+
hideOnEmpty = input(false, { ...(ngDevMode ? { debugName: "hideOnEmpty" } : {}), transform: booleanAttribute });
|
|
7884
|
+
/** When true the query text is cleared after a selection (action-style use). */
|
|
7885
|
+
clearQueryOnSelect = input(false, { ...(ngDevMode ? { debugName: "clearQueryOnSelect" } : {}), transform: booleanAttribute });
|
|
7886
|
+
/** Render the leading search icon. */
|
|
7887
|
+
showSearchIcon = input(true, { ...(ngDevMode ? { debugName: "showSearchIcon" } : {}), transform: booleanAttribute });
|
|
7888
|
+
/** Render the trailing clear (×) button when the query is non-empty. */
|
|
7889
|
+
showClear = input(true, { ...(ngDevMode ? { debugName: "showClear" } : {}), transform: booleanAttribute });
|
|
7890
|
+
/** Accessible label for the clear button. */
|
|
7891
|
+
clearAriaLabel = input('Clear search', ...(ngDevMode ? [{ debugName: "clearAriaLabel" }] : []));
|
|
7892
|
+
/** Maps a group key to its display heading; falls back to the key itself. */
|
|
7893
|
+
groupLabels = input({}, ...(ngDevMode ? [{ debugName: "groupLabels" }] : []));
|
|
7894
|
+
/** Explicit group ordering by key; unlisted keys keep first-seen order. */
|
|
7895
|
+
groupOrder = input([], ...(ngDevMode ? [{ debugName: "groupOrder" }] : []));
|
|
7896
|
+
/** Unique base id for the field's `id`/`aria` wiring. */
|
|
7897
|
+
autocompleteId = input(`af-autocomplete-${AfAutocompleteComponent.nextId++}`, ...(ngDevMode ? [{ debugName: "autocompleteId" }] : []));
|
|
7898
|
+
/** Two-way bound query text. The consumer debounces this and feeds `options`. */
|
|
7899
|
+
query = model('', ...(ngDevMode ? [{ debugName: "query" }] : []));
|
|
7900
|
+
/** Emitted when the user selects an enabled option. */
|
|
7901
|
+
optionSelected = output();
|
|
7902
|
+
optionTemplate = contentChild(AfAutocompleteOptionDirective, ...(ngDevMode ? [{ debugName: "optionTemplate" }] : []));
|
|
7903
|
+
inputRef = viewChild('inputEl', ...(ngDevMode ? [{ debugName: "inputRef" }] : []));
|
|
7904
|
+
isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
|
|
7905
|
+
highlightedIndex = signal(-1, ...(ngDevMode ? [{ debugName: "highlightedIndex" }] : []));
|
|
7906
|
+
get inputId() {
|
|
7907
|
+
return `${this.autocompleteId()}-input`;
|
|
7908
|
+
}
|
|
7909
|
+
get listboxId() {
|
|
7910
|
+
return `${this.autocompleteId()}-listbox`;
|
|
7911
|
+
}
|
|
7912
|
+
get hintId() {
|
|
7913
|
+
return `${this.autocompleteId()}-hint`;
|
|
7914
|
+
}
|
|
7915
|
+
get errorId() {
|
|
7916
|
+
return `${this.autocompleteId()}-error`;
|
|
7917
|
+
}
|
|
7918
|
+
rootClasses = computed(() => {
|
|
7919
|
+
const classes = ['ct-autocomplete'];
|
|
7920
|
+
const s = this.size();
|
|
7921
|
+
if (s === 'sm')
|
|
7922
|
+
classes.push('ct-autocomplete--sm');
|
|
7923
|
+
if (s === 'lg')
|
|
7924
|
+
classes.push('ct-autocomplete--lg');
|
|
7925
|
+
return classes.join(' ');
|
|
7926
|
+
}, ...(ngDevMode ? [{ debugName: "rootClasses" }] : []));
|
|
7927
|
+
/** True once the trimmed query reaches {@link minChars}. */
|
|
7928
|
+
hasMinChars = computed(() => this.query().trim().length >= this.minChars(), ...(ngDevMode ? [{ debugName: "hasMinChars" }] : []));
|
|
7929
|
+
/** Flat, display-ordered options used for keyboard navigation. */
|
|
7930
|
+
flatOptions = computed(() => this.options(), ...(ngDevMode ? [{ debugName: "flatOptions" }] : []));
|
|
7931
|
+
/** Options grouped for rendering, each paired with its flat index. */
|
|
7932
|
+
displayGroups = computed(() => {
|
|
7933
|
+
const order = this.groupOrder();
|
|
7934
|
+
const labels = this.groupLabels();
|
|
7935
|
+
const groups = new Map();
|
|
7936
|
+
this.options().forEach((option, flatIndex) => {
|
|
7937
|
+
const key = option.group ?? '';
|
|
7938
|
+
let group = groups.get(key);
|
|
7939
|
+
if (!group) {
|
|
7940
|
+
group = { key, label: key ? (labels[key] ?? key) : undefined, items: [] };
|
|
7941
|
+
groups.set(key, group);
|
|
7942
|
+
}
|
|
7943
|
+
group.items.push({ option, flatIndex });
|
|
7944
|
+
});
|
|
7945
|
+
const result = [...groups.values()];
|
|
7946
|
+
if (order.length > 0) {
|
|
7947
|
+
result.sort((a, b) => {
|
|
7948
|
+
const ia = order.indexOf(a.key);
|
|
7949
|
+
const ib = order.indexOf(b.key);
|
|
7950
|
+
return (ia === -1 ? order.length : ia) - (ib === -1 ? order.length : ib);
|
|
7951
|
+
});
|
|
7952
|
+
}
|
|
7953
|
+
return result;
|
|
7954
|
+
}, ...(ngDevMode ? [{ debugName: "displayGroups" }] : []));
|
|
7955
|
+
/** Whether the listbox is visually open. */
|
|
7956
|
+
panelOpen = computed(() => {
|
|
7957
|
+
if (!this.isOpen() || !this.hasMinChars())
|
|
7958
|
+
return false;
|
|
7959
|
+
if (this.hideOnEmpty() && !this.loading() && this.flatOptions().length === 0)
|
|
7960
|
+
return false;
|
|
7961
|
+
return true;
|
|
7962
|
+
}, ...(ngDevMode ? [{ debugName: "panelOpen" }] : []));
|
|
7963
|
+
/** Whether to render the "no results" row. */
|
|
7964
|
+
showEmpty = computed(() => !this.loading() && this.flatOptions().length === 0 && this.hasMinChars(), ...(ngDevMode ? [{ debugName: "showEmpty" }] : []));
|
|
7965
|
+
activeDescendantId = computed(() => {
|
|
7966
|
+
const idx = this.highlightedIndex();
|
|
7967
|
+
if (!this.panelOpen() || idx < 0 || idx >= this.flatOptions().length)
|
|
7968
|
+
return null;
|
|
7969
|
+
return this.optionDomId(idx);
|
|
7970
|
+
}, ...(ngDevMode ? [{ debugName: "activeDescendantId" }] : []));
|
|
7971
|
+
statusMessage = computed(() => {
|
|
7972
|
+
if (!this.panelOpen())
|
|
7973
|
+
return '';
|
|
7974
|
+
if (this.loading())
|
|
7975
|
+
return this.loadingText();
|
|
7976
|
+
const count = this.flatOptions().length;
|
|
7977
|
+
if (count === 0)
|
|
7978
|
+
return this.emptyText();
|
|
7979
|
+
return `${count} result${count === 1 ? '' : 's'} available`;
|
|
7980
|
+
}, ...(ngDevMode ? [{ debugName: "statusMessage" }] : []));
|
|
7981
|
+
ariaDescribedBy() {
|
|
7982
|
+
if (this.error())
|
|
7983
|
+
return this.errorId;
|
|
7984
|
+
if (this.hint())
|
|
7985
|
+
return this.hintId;
|
|
7986
|
+
return null;
|
|
7987
|
+
}
|
|
7988
|
+
optionDomId(flatIndex) {
|
|
7989
|
+
return `${this.autocompleteId()}-option-${flatIndex}`;
|
|
7990
|
+
}
|
|
7991
|
+
groupLabelId(groupIndex) {
|
|
7992
|
+
return `${this.autocompleteId()}-group-${groupIndex}`;
|
|
7993
|
+
}
|
|
7994
|
+
onInput(event) {
|
|
7995
|
+
const value = event.target.value;
|
|
7996
|
+
this.query.set(value);
|
|
7997
|
+
// Manual-selection pattern (WAI-ARIA APG): keep nothing highlighted until
|
|
7998
|
+
// the user navigates, so a free-text Enter never hijacks into a selection.
|
|
7999
|
+
this.highlightedIndex.set(-1);
|
|
8000
|
+
this.isOpen.set(value.trim().length >= this.minChars());
|
|
8001
|
+
}
|
|
8002
|
+
onFocus() {
|
|
8003
|
+
if (this.openOnFocus() && this.hasMinChars()) {
|
|
8004
|
+
this.isOpen.set(true);
|
|
8005
|
+
}
|
|
8006
|
+
}
|
|
8007
|
+
/** Selects an enabled option: emits it, then clears or restores the text. */
|
|
8008
|
+
selectOption(option) {
|
|
8009
|
+
if (option.disabled)
|
|
8010
|
+
return;
|
|
8011
|
+
this.optionSelected.emit(option);
|
|
8012
|
+
this.query.set(this.clearQueryOnSelect() ? '' : option.label);
|
|
8013
|
+
this.close();
|
|
8014
|
+
this.inputRef()?.nativeElement.focus();
|
|
8015
|
+
}
|
|
8016
|
+
/** Clears the query text and closes the listbox. */
|
|
8017
|
+
clear() {
|
|
8018
|
+
this.query.set('');
|
|
8019
|
+
this.close();
|
|
8020
|
+
this.inputRef()?.nativeElement.focus();
|
|
8021
|
+
}
|
|
8022
|
+
/** Closes the listbox without changing the query. */
|
|
8023
|
+
close() {
|
|
8024
|
+
this.isOpen.set(false);
|
|
8025
|
+
this.highlightedIndex.set(-1);
|
|
8026
|
+
}
|
|
8027
|
+
onKeydown(event) {
|
|
8028
|
+
const options = this.flatOptions();
|
|
8029
|
+
switch (event.key) {
|
|
8030
|
+
case 'ArrowDown':
|
|
8031
|
+
event.preventDefault();
|
|
8032
|
+
if (!this.panelOpen()) {
|
|
8033
|
+
this.isOpen.set(true);
|
|
8034
|
+
this.highlightedIndex.set(this.firstEnabledIndex());
|
|
8035
|
+
}
|
|
8036
|
+
else {
|
|
8037
|
+
this.moveHighlight(1);
|
|
8038
|
+
}
|
|
8039
|
+
break;
|
|
8040
|
+
case 'ArrowUp':
|
|
8041
|
+
event.preventDefault();
|
|
8042
|
+
if (!this.panelOpen()) {
|
|
8043
|
+
this.isOpen.set(true);
|
|
8044
|
+
this.highlightedIndex.set(this.lastEnabledIndex());
|
|
8045
|
+
}
|
|
8046
|
+
else {
|
|
8047
|
+
this.moveHighlight(-1);
|
|
8048
|
+
}
|
|
8049
|
+
break;
|
|
8050
|
+
case 'Enter': {
|
|
8051
|
+
const idx = this.highlightedIndex();
|
|
8052
|
+
if (this.panelOpen() && idx >= 0 && idx < options.length && !options[idx].disabled) {
|
|
8053
|
+
event.preventDefault();
|
|
8054
|
+
this.selectOption(options[idx]);
|
|
8055
|
+
}
|
|
8056
|
+
break;
|
|
8057
|
+
}
|
|
8058
|
+
case 'Escape':
|
|
8059
|
+
if (this.panelOpen()) {
|
|
8060
|
+
event.preventDefault();
|
|
8061
|
+
this.close();
|
|
8062
|
+
}
|
|
8063
|
+
break;
|
|
8064
|
+
case 'Home':
|
|
8065
|
+
if (this.panelOpen()) {
|
|
8066
|
+
event.preventDefault();
|
|
8067
|
+
this.highlightedIndex.set(this.firstEnabledIndex());
|
|
8068
|
+
this.scrollHighlightedIntoView();
|
|
8069
|
+
}
|
|
8070
|
+
break;
|
|
8071
|
+
case 'End':
|
|
8072
|
+
if (this.panelOpen()) {
|
|
8073
|
+
event.preventDefault();
|
|
8074
|
+
this.highlightedIndex.set(this.lastEnabledIndex());
|
|
8075
|
+
this.scrollHighlightedIntoView();
|
|
8076
|
+
}
|
|
8077
|
+
break;
|
|
8078
|
+
case 'Tab':
|
|
8079
|
+
if (this.panelOpen()) {
|
|
8080
|
+
const idx = this.highlightedIndex();
|
|
8081
|
+
if (idx >= 0 && idx < options.length && !options[idx].disabled) {
|
|
8082
|
+
this.selectOption(options[idx]);
|
|
8083
|
+
}
|
|
8084
|
+
this.close();
|
|
8085
|
+
}
|
|
8086
|
+
break;
|
|
8087
|
+
}
|
|
8088
|
+
}
|
|
8089
|
+
onDocumentClick(event) {
|
|
8090
|
+
const host = this.inputRef()?.nativeElement.closest('.ct-autocomplete');
|
|
8091
|
+
if (host && !host.contains(event.target)) {
|
|
8092
|
+
this.close();
|
|
8093
|
+
}
|
|
8094
|
+
}
|
|
8095
|
+
moveHighlight(direction) {
|
|
8096
|
+
const options = this.flatOptions();
|
|
8097
|
+
if (options.length === 0)
|
|
8098
|
+
return;
|
|
8099
|
+
let idx = this.highlightedIndex();
|
|
8100
|
+
let attempts = 0;
|
|
8101
|
+
do {
|
|
8102
|
+
idx += direction;
|
|
8103
|
+
if (idx < 0)
|
|
8104
|
+
idx = options.length - 1;
|
|
8105
|
+
if (idx >= options.length)
|
|
8106
|
+
idx = 0;
|
|
8107
|
+
attempts++;
|
|
8108
|
+
} while (options[idx]?.disabled && attempts <= options.length);
|
|
8109
|
+
if (!options[idx]?.disabled) {
|
|
8110
|
+
this.highlightedIndex.set(idx);
|
|
8111
|
+
this.scrollHighlightedIntoView();
|
|
8112
|
+
}
|
|
8113
|
+
}
|
|
8114
|
+
firstEnabledIndex() {
|
|
8115
|
+
return this.flatOptions().findIndex((o) => !o.disabled);
|
|
8116
|
+
}
|
|
8117
|
+
lastEnabledIndex() {
|
|
8118
|
+
const options = this.flatOptions();
|
|
8119
|
+
for (let i = options.length - 1; i >= 0; i--) {
|
|
8120
|
+
if (!options[i].disabled)
|
|
8121
|
+
return i;
|
|
8122
|
+
}
|
|
8123
|
+
return -1;
|
|
8124
|
+
}
|
|
8125
|
+
scrollHighlightedIntoView() {
|
|
8126
|
+
queueMicrotask(() => {
|
|
8127
|
+
const host = this.inputRef()?.nativeElement.closest('.ct-autocomplete');
|
|
8128
|
+
const optionEl = host?.querySelector('[data-highlighted]');
|
|
8129
|
+
optionEl?.scrollIntoView?.({ block: 'nearest' });
|
|
8130
|
+
});
|
|
8131
|
+
}
|
|
8132
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: AfAutocompleteComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
8133
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: AfAutocompleteComponent, isStandalone: true, selector: "af-autocomplete", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, minChars: { classPropertyName: "minChars", publicName: "minChars", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, loadingText: { classPropertyName: "loadingText", publicName: "loadingText", isSignal: true, isRequired: false, transformFunction: null }, openOnFocus: { classPropertyName: "openOnFocus", publicName: "openOnFocus", isSignal: true, isRequired: false, transformFunction: null }, hideOnEmpty: { classPropertyName: "hideOnEmpty", publicName: "hideOnEmpty", isSignal: true, isRequired: false, transformFunction: null }, clearQueryOnSelect: { classPropertyName: "clearQueryOnSelect", publicName: "clearQueryOnSelect", isSignal: true, isRequired: false, transformFunction: null }, showSearchIcon: { classPropertyName: "showSearchIcon", publicName: "showSearchIcon", isSignal: true, isRequired: false, transformFunction: null }, showClear: { classPropertyName: "showClear", publicName: "showClear", isSignal: true, isRequired: false, transformFunction: null }, clearAriaLabel: { classPropertyName: "clearAriaLabel", publicName: "clearAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, groupLabels: { classPropertyName: "groupLabels", publicName: "groupLabels", isSignal: true, isRequired: false, transformFunction: null }, groupOrder: { classPropertyName: "groupOrder", publicName: "groupOrder", isSignal: true, isRequired: false, transformFunction: null }, autocompleteId: { classPropertyName: "autocompleteId", publicName: "autocompleteId", isSignal: true, isRequired: false, transformFunction: null }, query: { classPropertyName: "query", publicName: "query", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { disabled: "disabledChange", query: "queryChange", optionSelected: "optionSelected" }, host: { listeners: { "document:click": "onDocumentClick($event)" } }, queries: [{ propertyName: "optionTemplate", first: true, predicate: AfAutocompleteOptionDirective, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["inputEl"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
8134
|
+
<div class="ct-field" [class.ct-field--error]="error()">
|
|
8135
|
+
@if (label()) {
|
|
8136
|
+
<label class="ct-field__label" [attr.for]="inputId">
|
|
8137
|
+
{{ label() }}
|
|
8138
|
+
@if (required()) {
|
|
8139
|
+
<span aria-label="required"> *</span>
|
|
8140
|
+
}
|
|
8141
|
+
</label>
|
|
8142
|
+
}
|
|
8143
|
+
|
|
8144
|
+
<div [class]="rootClasses()" [attr.data-state]="panelOpen() ? 'open' : 'closed'">
|
|
8145
|
+
<div class="ct-autocomplete__input-wrap">
|
|
8146
|
+
@if (showSearchIcon()) {
|
|
8147
|
+
<span class="ct-autocomplete__leading" aria-hidden="true">
|
|
8148
|
+
<svg viewBox="0 0 16 16" fill="none">
|
|
8149
|
+
<circle cx="7" cy="7" r="4.5" stroke="currentColor" stroke-width="1.5" />
|
|
8150
|
+
<path
|
|
8151
|
+
d="M10.5 10.5L14 14"
|
|
8152
|
+
stroke="currentColor"
|
|
8153
|
+
stroke-width="1.5"
|
|
8154
|
+
stroke-linecap="round"
|
|
8155
|
+
/>
|
|
8156
|
+
</svg>
|
|
8157
|
+
</span>
|
|
8158
|
+
}
|
|
8159
|
+
<input
|
|
8160
|
+
#inputEl
|
|
8161
|
+
class="ct-autocomplete__input"
|
|
8162
|
+
[class.ct-autocomplete__input--with-leading]="showSearchIcon()"
|
|
8163
|
+
[id]="inputId"
|
|
8164
|
+
type="text"
|
|
8165
|
+
role="combobox"
|
|
8166
|
+
autocomplete="off"
|
|
8167
|
+
autocapitalize="off"
|
|
8168
|
+
spellcheck="false"
|
|
8169
|
+
[attr.aria-expanded]="panelOpen()"
|
|
8170
|
+
[attr.aria-controls]="listboxId"
|
|
8171
|
+
aria-autocomplete="list"
|
|
8172
|
+
[attr.aria-activedescendant]="activeDescendantId()"
|
|
8173
|
+
[attr.aria-invalid]="error() ? true : null"
|
|
8174
|
+
[attr.aria-describedby]="ariaDescribedBy()"
|
|
8175
|
+
[attr.aria-busy]="loading() || null"
|
|
8176
|
+
[attr.aria-required]="required() || null"
|
|
8177
|
+
[placeholder]="placeholder()"
|
|
8178
|
+
[disabled]="disabled()"
|
|
8179
|
+
[value]="query()"
|
|
8180
|
+
(input)="onInput($event)"
|
|
8181
|
+
(focus)="onFocus()"
|
|
8182
|
+
(keydown)="onKeydown($event)"
|
|
8183
|
+
/>
|
|
8184
|
+
|
|
8185
|
+
@if (loading()) {
|
|
8186
|
+
<span class="ct-autocomplete__spinner" aria-hidden="true"></span>
|
|
8187
|
+
} @else if (showClear() && query()) {
|
|
8188
|
+
<button
|
|
8189
|
+
type="button"
|
|
8190
|
+
class="ct-autocomplete__clear"
|
|
8191
|
+
[attr.aria-label]="clearAriaLabel()"
|
|
8192
|
+
(click)="clear()"
|
|
8193
|
+
(mousedown)="$event.preventDefault()"
|
|
8194
|
+
>
|
|
8195
|
+
<svg viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
8196
|
+
<path
|
|
8197
|
+
d="M4 4l8 8M12 4l-8 8"
|
|
8198
|
+
stroke="currentColor"
|
|
8199
|
+
stroke-width="1.5"
|
|
8200
|
+
stroke-linecap="round"
|
|
8201
|
+
/>
|
|
8202
|
+
</svg>
|
|
8203
|
+
</button>
|
|
8204
|
+
}
|
|
8205
|
+
</div>
|
|
8206
|
+
|
|
8207
|
+
<div
|
|
8208
|
+
#listboxEl
|
|
8209
|
+
[id]="listboxId"
|
|
8210
|
+
class="ct-autocomplete__listbox"
|
|
8211
|
+
role="listbox"
|
|
8212
|
+
[attr.aria-label]="label() || placeholder() || 'Suggestions'"
|
|
8213
|
+
[attr.aria-busy]="loading() || null"
|
|
8214
|
+
>
|
|
8215
|
+
@if (panelOpen()) {
|
|
8216
|
+
@for (group of displayGroups(); track group.key; let gi = $index) {
|
|
8217
|
+
@if (group.label) {
|
|
8218
|
+
<div
|
|
8219
|
+
class="ct-autocomplete__group"
|
|
8220
|
+
role="group"
|
|
8221
|
+
[attr.aria-labelledby]="groupLabelId(gi)"
|
|
8222
|
+
>
|
|
8223
|
+
<div class="ct-autocomplete__group-label" [id]="groupLabelId(gi)">
|
|
8224
|
+
{{ group.label }}
|
|
8225
|
+
</div>
|
|
8226
|
+
@for (item of group.items; track item.option.id) {
|
|
8227
|
+
<ng-container
|
|
8228
|
+
[ngTemplateOutlet]="optionRow"
|
|
8229
|
+
[ngTemplateOutletContext]="{ $implicit: item }"
|
|
8230
|
+
/>
|
|
8231
|
+
}
|
|
8232
|
+
</div>
|
|
8233
|
+
} @else {
|
|
8234
|
+
@for (item of group.items; track item.option.id) {
|
|
8235
|
+
<ng-container
|
|
8236
|
+
[ngTemplateOutlet]="optionRow"
|
|
8237
|
+
[ngTemplateOutletContext]="{ $implicit: item }"
|
|
8238
|
+
/>
|
|
8239
|
+
}
|
|
8240
|
+
}
|
|
8241
|
+
}
|
|
8242
|
+
|
|
8243
|
+
@if (loading()) {
|
|
8244
|
+
<div class="ct-autocomplete__loading" role="presentation">
|
|
8245
|
+
<span class="ct-autocomplete__spinner" aria-hidden="true"></span>
|
|
8246
|
+
{{ loadingText() }}
|
|
8247
|
+
</div>
|
|
8248
|
+
} @else if (showEmpty()) {
|
|
8249
|
+
<div class="ct-autocomplete__empty" role="presentation">{{ emptyText() }}</div>
|
|
8250
|
+
}
|
|
8251
|
+
}
|
|
8252
|
+
</div>
|
|
8253
|
+
|
|
8254
|
+
<div class="ct-autocomplete__status" role="status" aria-live="polite" aria-atomic="true">
|
|
8255
|
+
{{ statusMessage() }}
|
|
8256
|
+
</div>
|
|
8257
|
+
</div>
|
|
8258
|
+
|
|
8259
|
+
@if (hint() && !error()) {
|
|
8260
|
+
<div class="ct-field__hint" [id]="hintId">{{ hint() }}</div>
|
|
8261
|
+
}
|
|
8262
|
+
@if (error()) {
|
|
8263
|
+
<div class="ct-field__error" role="alert" [id]="errorId">{{ error() }}</div>
|
|
8264
|
+
}
|
|
8265
|
+
</div>
|
|
8266
|
+
|
|
8267
|
+
<ng-template #optionRow let-item>
|
|
8268
|
+
<div
|
|
8269
|
+
class="ct-autocomplete__option"
|
|
8270
|
+
role="option"
|
|
8271
|
+
[id]="optionDomId(item.flatIndex)"
|
|
8272
|
+
[attr.aria-selected]="highlightedIndex() === item.flatIndex"
|
|
8273
|
+
[attr.aria-disabled]="item.option.disabled || null"
|
|
8274
|
+
[attr.data-highlighted]="highlightedIndex() === item.flatIndex ? '' : null"
|
|
8275
|
+
(click)="selectOption(item.option)"
|
|
8276
|
+
(mouseenter)="highlightedIndex.set(item.flatIndex)"
|
|
8277
|
+
(mousedown)="$event.preventDefault()"
|
|
8278
|
+
>
|
|
8279
|
+
@if (optionTemplate(); as tpl) {
|
|
8280
|
+
<ng-container
|
|
8281
|
+
[ngTemplateOutlet]="tpl.template"
|
|
8282
|
+
[ngTemplateOutletContext]="{
|
|
8283
|
+
$implicit: item.option,
|
|
8284
|
+
option: item.option,
|
|
8285
|
+
query: query(),
|
|
8286
|
+
}"
|
|
8287
|
+
/>
|
|
8288
|
+
} @else {
|
|
8289
|
+
<span class="ct-autocomplete__option-label">{{ item.option.label }}</span>
|
|
8290
|
+
@if (item.option.description) {
|
|
8291
|
+
<span class="ct-autocomplete__option-description">{{ item.option.description }}</span>
|
|
8292
|
+
}
|
|
8293
|
+
}
|
|
8294
|
+
</div>
|
|
8295
|
+
</ng-template>
|
|
8296
|
+
`, isInline: true, styles: [":host{display:block}.ct-autocomplete{--ct-ac-height: var(--control-height-md);--ct-ac-font-size: var(--font-size-md);--ct-ac-line-height: var(--line-height-md);--ct-ac-padding-x: var(--space-5);--ct-ac-option-padding-x: var(--space-4);--ct-ac-option-padding-y: var(--space-3);--ct-ac-listbox-max-height: 320px;position:relative;display:flex;flex-direction:column;width:100%}.ct-autocomplete--sm{--ct-ac-height: var(--control-height-sm);--ct-ac-font-size: var(--font-size-sm);--ct-ac-line-height: var(--line-height-sm);--ct-ac-padding-x: var(--space-4);--ct-ac-option-padding-y: var(--space-2)}.ct-autocomplete--lg{--ct-ac-height: var(--control-height-lg);--ct-ac-font-size: var(--font-size-lg);--ct-ac-line-height: var(--line-height-lg);--ct-ac-padding-x: var(--space-6);--ct-ac-option-padding-y: var(--space-4)}.ct-autocomplete__input-wrap{position:relative;display:flex;align-items:center}.ct-autocomplete__leading{position:absolute;inset-inline-start:var(--ct-ac-padding-x);display:inline-flex;align-items:center;justify-content:center;color:var(--color-text-muted);pointer-events:none}.ct-autocomplete__leading svg{width:var(--icon-sm, 1rem);height:var(--icon-sm, 1rem)}.ct-autocomplete__input{width:100%;height:var(--ct-ac-height);padding:0 var(--ct-ac-padding-x);padding-inline-end:calc(var(--ct-ac-padding-x) + var(--icon-md, 1.25rem) + var(--space-2));border:var(--border-thin) solid var(--ct-control-border);border-radius:var(--ct-control-radius);background:var(--ct-control-bg);color:var(--ct-control-text);font-size:var(--ct-ac-font-size);line-height:var(--ct-ac-line-height);transition:var(--ct-control-transition)}.ct-autocomplete__input--with-leading{padding-inline-start:calc(var(--ct-ac-padding-x) + var(--icon-sm, 1rem) + var(--space-2))}.ct-autocomplete__input::placeholder{color:var(--ct-control-placeholder)}@media(hover:hover){.ct-autocomplete__input:hover{border-color:var(--ct-control-border-hover)}}.ct-autocomplete__input:focus-visible{border-color:var(--ct-control-border-focus);outline:2px solid var(--color-focus-ring);outline-offset:-1px}.ct-autocomplete__input:disabled{background:var(--ct-control-bg-disabled);border-color:var(--color-border-subtle);color:var(--color-text-muted);pointer-events:none}.ct-autocomplete__input[aria-invalid=true]{border-color:var(--color-state-danger)}.ct-autocomplete__input[aria-invalid=true]:focus-visible{outline-color:var(--color-state-danger)}.ct-autocomplete__clear{appearance:none;position:absolute;inset-inline-end:var(--space-2);display:inline-flex;align-items:center;justify-content:center;width:var(--icon-lg, 1.5rem);height:var(--icon-lg, 1.5rem);padding:0;border:none;border-radius:var(--radius-sm);background:transparent;color:var(--color-text-muted);cursor:pointer;transition:color var(--duration-fast) var(--easing-standard),background var(--duration-fast) var(--easing-standard)}.ct-autocomplete__clear svg{width:var(--icon-sm, 1rem);height:var(--icon-sm, 1rem)}@media(hover:hover){.ct-autocomplete__clear:hover{color:var(--color-text-secondary);background:var(--color-bg-muted)}}.ct-autocomplete__clear:focus-visible{outline:2px solid var(--color-focus-ring);outline-offset:1px}.ct-autocomplete__spinner{position:absolute;inset-inline-end:var(--space-4);width:var(--icon-sm, 1rem);height:var(--icon-sm, 1rem);border:2px solid var(--color-border-subtle);border-top-color:var(--color-brand-primary);border-radius:var(--radius-full, 999px);animation:ct-ac-spin .6s linear infinite}.ct-autocomplete__loading .ct-autocomplete__spinner,.ct-autocomplete__empty .ct-autocomplete__spinner{position:static;inset-inline-end:auto}@keyframes ct-ac-spin{to{transform:rotate(360deg)}}.ct-autocomplete__listbox{position:absolute;inset-inline-start:0;inset-inline-end:0;inset-block-start:calc(100% + var(--space-2));z-index:var(--z-dropdown, 50);max-height:var(--ct-ac-listbox-max-height);overflow-y:auto;padding:var(--space-2);border-radius:var(--radius-md);border:var(--border-thin) solid var(--color-border-subtle);background:var(--color-bg-elevated);box-shadow:var(--shadow-dropdown);opacity:0;visibility:hidden;transform:translateY(4px);transition:opacity var(--duration-fast) var(--easing-standard),transform var(--duration-fast) var(--easing-standard),visibility 0s linear var(--duration-fast)}.ct-autocomplete[data-state=open] .ct-autocomplete__listbox{opacity:1;visibility:visible;transform:translateY(0);transition:opacity var(--duration-fast) var(--easing-standard),transform var(--duration-fast) var(--easing-standard),visibility 0s linear 0s}.ct-autocomplete__group+.ct-autocomplete__group{margin-top:var(--space-1);border-top:var(--border-thin) solid var(--color-border-subtle);padding-top:var(--space-1)}.ct-autocomplete__group-label{padding:var(--space-2) var(--ct-ac-option-padding-x);font-size:var(--font-size-xs);font-weight:var(--font-weight-semibold);color:var(--color-text-muted);text-transform:uppercase;letter-spacing:var(--letter-spacing-wide, .04em)}.ct-autocomplete__option{display:flex;align-items:center;gap:var(--space-3);padding:var(--ct-ac-option-padding-y) var(--ct-ac-option-padding-x);border-radius:var(--radius-sm);color:var(--color-text-primary);font-size:var(--ct-ac-font-size);line-height:var(--ct-ac-line-height);cursor:pointer;transition:background var(--duration-fast) var(--easing-standard)}@media(hover:hover){.ct-autocomplete__option:hover{background:var(--color-bg-muted)}}.ct-autocomplete__option[data-highlighted]{background:var(--color-bg-muted);outline:var(--border-medium) solid var(--color-brand-primary);outline-offset:-2px}.ct-autocomplete__option[aria-disabled=true]{opacity:var(--opacity-disabled, .5);pointer-events:none;cursor:not-allowed}.ct-autocomplete__option-label{flex:1;min-width:0}.ct-autocomplete__option-description{font-size:var(--font-size-xs);color:var(--color-text-muted)}.ct-autocomplete__empty,.ct-autocomplete__loading{padding:var(--space-6) var(--ct-ac-option-padding-x);text-align:center;color:var(--color-text-muted);font-size:var(--font-size-sm)}.ct-autocomplete__loading{display:flex;align-items:center;justify-content:center;gap:var(--space-3)}.ct-autocomplete__status{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);border:0}@media(prefers-reduced-motion:reduce){.ct-autocomplete__listbox{transition:none}.ct-autocomplete__spinner{animation-duration:1.5s}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
8297
|
+
}
|
|
8298
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: AfAutocompleteComponent, decorators: [{
|
|
8299
|
+
type: Component,
|
|
8300
|
+
args: [{ selector: 'af-autocomplete', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgTemplateOutlet], host: {
|
|
8301
|
+
'(document:click)': 'onDocumentClick($event)',
|
|
8302
|
+
}, template: `
|
|
8303
|
+
<div class="ct-field" [class.ct-field--error]="error()">
|
|
8304
|
+
@if (label()) {
|
|
8305
|
+
<label class="ct-field__label" [attr.for]="inputId">
|
|
8306
|
+
{{ label() }}
|
|
8307
|
+
@if (required()) {
|
|
8308
|
+
<span aria-label="required"> *</span>
|
|
8309
|
+
}
|
|
8310
|
+
</label>
|
|
8311
|
+
}
|
|
8312
|
+
|
|
8313
|
+
<div [class]="rootClasses()" [attr.data-state]="panelOpen() ? 'open' : 'closed'">
|
|
8314
|
+
<div class="ct-autocomplete__input-wrap">
|
|
8315
|
+
@if (showSearchIcon()) {
|
|
8316
|
+
<span class="ct-autocomplete__leading" aria-hidden="true">
|
|
8317
|
+
<svg viewBox="0 0 16 16" fill="none">
|
|
8318
|
+
<circle cx="7" cy="7" r="4.5" stroke="currentColor" stroke-width="1.5" />
|
|
8319
|
+
<path
|
|
8320
|
+
d="M10.5 10.5L14 14"
|
|
8321
|
+
stroke="currentColor"
|
|
8322
|
+
stroke-width="1.5"
|
|
8323
|
+
stroke-linecap="round"
|
|
8324
|
+
/>
|
|
8325
|
+
</svg>
|
|
8326
|
+
</span>
|
|
8327
|
+
}
|
|
8328
|
+
<input
|
|
8329
|
+
#inputEl
|
|
8330
|
+
class="ct-autocomplete__input"
|
|
8331
|
+
[class.ct-autocomplete__input--with-leading]="showSearchIcon()"
|
|
8332
|
+
[id]="inputId"
|
|
8333
|
+
type="text"
|
|
8334
|
+
role="combobox"
|
|
8335
|
+
autocomplete="off"
|
|
8336
|
+
autocapitalize="off"
|
|
8337
|
+
spellcheck="false"
|
|
8338
|
+
[attr.aria-expanded]="panelOpen()"
|
|
8339
|
+
[attr.aria-controls]="listboxId"
|
|
8340
|
+
aria-autocomplete="list"
|
|
8341
|
+
[attr.aria-activedescendant]="activeDescendantId()"
|
|
8342
|
+
[attr.aria-invalid]="error() ? true : null"
|
|
8343
|
+
[attr.aria-describedby]="ariaDescribedBy()"
|
|
8344
|
+
[attr.aria-busy]="loading() || null"
|
|
8345
|
+
[attr.aria-required]="required() || null"
|
|
8346
|
+
[placeholder]="placeholder()"
|
|
8347
|
+
[disabled]="disabled()"
|
|
8348
|
+
[value]="query()"
|
|
8349
|
+
(input)="onInput($event)"
|
|
8350
|
+
(focus)="onFocus()"
|
|
8351
|
+
(keydown)="onKeydown($event)"
|
|
8352
|
+
/>
|
|
8353
|
+
|
|
8354
|
+
@if (loading()) {
|
|
8355
|
+
<span class="ct-autocomplete__spinner" aria-hidden="true"></span>
|
|
8356
|
+
} @else if (showClear() && query()) {
|
|
8357
|
+
<button
|
|
8358
|
+
type="button"
|
|
8359
|
+
class="ct-autocomplete__clear"
|
|
8360
|
+
[attr.aria-label]="clearAriaLabel()"
|
|
8361
|
+
(click)="clear()"
|
|
8362
|
+
(mousedown)="$event.preventDefault()"
|
|
8363
|
+
>
|
|
8364
|
+
<svg viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
8365
|
+
<path
|
|
8366
|
+
d="M4 4l8 8M12 4l-8 8"
|
|
8367
|
+
stroke="currentColor"
|
|
8368
|
+
stroke-width="1.5"
|
|
8369
|
+
stroke-linecap="round"
|
|
8370
|
+
/>
|
|
8371
|
+
</svg>
|
|
8372
|
+
</button>
|
|
8373
|
+
}
|
|
8374
|
+
</div>
|
|
8375
|
+
|
|
8376
|
+
<div
|
|
8377
|
+
#listboxEl
|
|
8378
|
+
[id]="listboxId"
|
|
8379
|
+
class="ct-autocomplete__listbox"
|
|
8380
|
+
role="listbox"
|
|
8381
|
+
[attr.aria-label]="label() || placeholder() || 'Suggestions'"
|
|
8382
|
+
[attr.aria-busy]="loading() || null"
|
|
8383
|
+
>
|
|
8384
|
+
@if (panelOpen()) {
|
|
8385
|
+
@for (group of displayGroups(); track group.key; let gi = $index) {
|
|
8386
|
+
@if (group.label) {
|
|
8387
|
+
<div
|
|
8388
|
+
class="ct-autocomplete__group"
|
|
8389
|
+
role="group"
|
|
8390
|
+
[attr.aria-labelledby]="groupLabelId(gi)"
|
|
8391
|
+
>
|
|
8392
|
+
<div class="ct-autocomplete__group-label" [id]="groupLabelId(gi)">
|
|
8393
|
+
{{ group.label }}
|
|
8394
|
+
</div>
|
|
8395
|
+
@for (item of group.items; track item.option.id) {
|
|
8396
|
+
<ng-container
|
|
8397
|
+
[ngTemplateOutlet]="optionRow"
|
|
8398
|
+
[ngTemplateOutletContext]="{ $implicit: item }"
|
|
8399
|
+
/>
|
|
8400
|
+
}
|
|
8401
|
+
</div>
|
|
8402
|
+
} @else {
|
|
8403
|
+
@for (item of group.items; track item.option.id) {
|
|
8404
|
+
<ng-container
|
|
8405
|
+
[ngTemplateOutlet]="optionRow"
|
|
8406
|
+
[ngTemplateOutletContext]="{ $implicit: item }"
|
|
8407
|
+
/>
|
|
8408
|
+
}
|
|
8409
|
+
}
|
|
8410
|
+
}
|
|
8411
|
+
|
|
8412
|
+
@if (loading()) {
|
|
8413
|
+
<div class="ct-autocomplete__loading" role="presentation">
|
|
8414
|
+
<span class="ct-autocomplete__spinner" aria-hidden="true"></span>
|
|
8415
|
+
{{ loadingText() }}
|
|
8416
|
+
</div>
|
|
8417
|
+
} @else if (showEmpty()) {
|
|
8418
|
+
<div class="ct-autocomplete__empty" role="presentation">{{ emptyText() }}</div>
|
|
8419
|
+
}
|
|
8420
|
+
}
|
|
8421
|
+
</div>
|
|
8422
|
+
|
|
8423
|
+
<div class="ct-autocomplete__status" role="status" aria-live="polite" aria-atomic="true">
|
|
8424
|
+
{{ statusMessage() }}
|
|
8425
|
+
</div>
|
|
8426
|
+
</div>
|
|
8427
|
+
|
|
8428
|
+
@if (hint() && !error()) {
|
|
8429
|
+
<div class="ct-field__hint" [id]="hintId">{{ hint() }}</div>
|
|
8430
|
+
}
|
|
8431
|
+
@if (error()) {
|
|
8432
|
+
<div class="ct-field__error" role="alert" [id]="errorId">{{ error() }}</div>
|
|
8433
|
+
}
|
|
8434
|
+
</div>
|
|
8435
|
+
|
|
8436
|
+
<ng-template #optionRow let-item>
|
|
8437
|
+
<div
|
|
8438
|
+
class="ct-autocomplete__option"
|
|
8439
|
+
role="option"
|
|
8440
|
+
[id]="optionDomId(item.flatIndex)"
|
|
8441
|
+
[attr.aria-selected]="highlightedIndex() === item.flatIndex"
|
|
8442
|
+
[attr.aria-disabled]="item.option.disabled || null"
|
|
8443
|
+
[attr.data-highlighted]="highlightedIndex() === item.flatIndex ? '' : null"
|
|
8444
|
+
(click)="selectOption(item.option)"
|
|
8445
|
+
(mouseenter)="highlightedIndex.set(item.flatIndex)"
|
|
8446
|
+
(mousedown)="$event.preventDefault()"
|
|
8447
|
+
>
|
|
8448
|
+
@if (optionTemplate(); as tpl) {
|
|
8449
|
+
<ng-container
|
|
8450
|
+
[ngTemplateOutlet]="tpl.template"
|
|
8451
|
+
[ngTemplateOutletContext]="{
|
|
8452
|
+
$implicit: item.option,
|
|
8453
|
+
option: item.option,
|
|
8454
|
+
query: query(),
|
|
8455
|
+
}"
|
|
8456
|
+
/>
|
|
8457
|
+
} @else {
|
|
8458
|
+
<span class="ct-autocomplete__option-label">{{ item.option.label }}</span>
|
|
8459
|
+
@if (item.option.description) {
|
|
8460
|
+
<span class="ct-autocomplete__option-description">{{ item.option.description }}</span>
|
|
8461
|
+
}
|
|
8462
|
+
}
|
|
8463
|
+
</div>
|
|
8464
|
+
</ng-template>
|
|
8465
|
+
`, styles: [":host{display:block}.ct-autocomplete{--ct-ac-height: var(--control-height-md);--ct-ac-font-size: var(--font-size-md);--ct-ac-line-height: var(--line-height-md);--ct-ac-padding-x: var(--space-5);--ct-ac-option-padding-x: var(--space-4);--ct-ac-option-padding-y: var(--space-3);--ct-ac-listbox-max-height: 320px;position:relative;display:flex;flex-direction:column;width:100%}.ct-autocomplete--sm{--ct-ac-height: var(--control-height-sm);--ct-ac-font-size: var(--font-size-sm);--ct-ac-line-height: var(--line-height-sm);--ct-ac-padding-x: var(--space-4);--ct-ac-option-padding-y: var(--space-2)}.ct-autocomplete--lg{--ct-ac-height: var(--control-height-lg);--ct-ac-font-size: var(--font-size-lg);--ct-ac-line-height: var(--line-height-lg);--ct-ac-padding-x: var(--space-6);--ct-ac-option-padding-y: var(--space-4)}.ct-autocomplete__input-wrap{position:relative;display:flex;align-items:center}.ct-autocomplete__leading{position:absolute;inset-inline-start:var(--ct-ac-padding-x);display:inline-flex;align-items:center;justify-content:center;color:var(--color-text-muted);pointer-events:none}.ct-autocomplete__leading svg{width:var(--icon-sm, 1rem);height:var(--icon-sm, 1rem)}.ct-autocomplete__input{width:100%;height:var(--ct-ac-height);padding:0 var(--ct-ac-padding-x);padding-inline-end:calc(var(--ct-ac-padding-x) + var(--icon-md, 1.25rem) + var(--space-2));border:var(--border-thin) solid var(--ct-control-border);border-radius:var(--ct-control-radius);background:var(--ct-control-bg);color:var(--ct-control-text);font-size:var(--ct-ac-font-size);line-height:var(--ct-ac-line-height);transition:var(--ct-control-transition)}.ct-autocomplete__input--with-leading{padding-inline-start:calc(var(--ct-ac-padding-x) + var(--icon-sm, 1rem) + var(--space-2))}.ct-autocomplete__input::placeholder{color:var(--ct-control-placeholder)}@media(hover:hover){.ct-autocomplete__input:hover{border-color:var(--ct-control-border-hover)}}.ct-autocomplete__input:focus-visible{border-color:var(--ct-control-border-focus);outline:2px solid var(--color-focus-ring);outline-offset:-1px}.ct-autocomplete__input:disabled{background:var(--ct-control-bg-disabled);border-color:var(--color-border-subtle);color:var(--color-text-muted);pointer-events:none}.ct-autocomplete__input[aria-invalid=true]{border-color:var(--color-state-danger)}.ct-autocomplete__input[aria-invalid=true]:focus-visible{outline-color:var(--color-state-danger)}.ct-autocomplete__clear{appearance:none;position:absolute;inset-inline-end:var(--space-2);display:inline-flex;align-items:center;justify-content:center;width:var(--icon-lg, 1.5rem);height:var(--icon-lg, 1.5rem);padding:0;border:none;border-radius:var(--radius-sm);background:transparent;color:var(--color-text-muted);cursor:pointer;transition:color var(--duration-fast) var(--easing-standard),background var(--duration-fast) var(--easing-standard)}.ct-autocomplete__clear svg{width:var(--icon-sm, 1rem);height:var(--icon-sm, 1rem)}@media(hover:hover){.ct-autocomplete__clear:hover{color:var(--color-text-secondary);background:var(--color-bg-muted)}}.ct-autocomplete__clear:focus-visible{outline:2px solid var(--color-focus-ring);outline-offset:1px}.ct-autocomplete__spinner{position:absolute;inset-inline-end:var(--space-4);width:var(--icon-sm, 1rem);height:var(--icon-sm, 1rem);border:2px solid var(--color-border-subtle);border-top-color:var(--color-brand-primary);border-radius:var(--radius-full, 999px);animation:ct-ac-spin .6s linear infinite}.ct-autocomplete__loading .ct-autocomplete__spinner,.ct-autocomplete__empty .ct-autocomplete__spinner{position:static;inset-inline-end:auto}@keyframes ct-ac-spin{to{transform:rotate(360deg)}}.ct-autocomplete__listbox{position:absolute;inset-inline-start:0;inset-inline-end:0;inset-block-start:calc(100% + var(--space-2));z-index:var(--z-dropdown, 50);max-height:var(--ct-ac-listbox-max-height);overflow-y:auto;padding:var(--space-2);border-radius:var(--radius-md);border:var(--border-thin) solid var(--color-border-subtle);background:var(--color-bg-elevated);box-shadow:var(--shadow-dropdown);opacity:0;visibility:hidden;transform:translateY(4px);transition:opacity var(--duration-fast) var(--easing-standard),transform var(--duration-fast) var(--easing-standard),visibility 0s linear var(--duration-fast)}.ct-autocomplete[data-state=open] .ct-autocomplete__listbox{opacity:1;visibility:visible;transform:translateY(0);transition:opacity var(--duration-fast) var(--easing-standard),transform var(--duration-fast) var(--easing-standard),visibility 0s linear 0s}.ct-autocomplete__group+.ct-autocomplete__group{margin-top:var(--space-1);border-top:var(--border-thin) solid var(--color-border-subtle);padding-top:var(--space-1)}.ct-autocomplete__group-label{padding:var(--space-2) var(--ct-ac-option-padding-x);font-size:var(--font-size-xs);font-weight:var(--font-weight-semibold);color:var(--color-text-muted);text-transform:uppercase;letter-spacing:var(--letter-spacing-wide, .04em)}.ct-autocomplete__option{display:flex;align-items:center;gap:var(--space-3);padding:var(--ct-ac-option-padding-y) var(--ct-ac-option-padding-x);border-radius:var(--radius-sm);color:var(--color-text-primary);font-size:var(--ct-ac-font-size);line-height:var(--ct-ac-line-height);cursor:pointer;transition:background var(--duration-fast) var(--easing-standard)}@media(hover:hover){.ct-autocomplete__option:hover{background:var(--color-bg-muted)}}.ct-autocomplete__option[data-highlighted]{background:var(--color-bg-muted);outline:var(--border-medium) solid var(--color-brand-primary);outline-offset:-2px}.ct-autocomplete__option[aria-disabled=true]{opacity:var(--opacity-disabled, .5);pointer-events:none;cursor:not-allowed}.ct-autocomplete__option-label{flex:1;min-width:0}.ct-autocomplete__option-description{font-size:var(--font-size-xs);color:var(--color-text-muted)}.ct-autocomplete__empty,.ct-autocomplete__loading{padding:var(--space-6) var(--ct-ac-option-padding-x);text-align:center;color:var(--color-text-muted);font-size:var(--font-size-sm)}.ct-autocomplete__loading{display:flex;align-items:center;justify-content:center;gap:var(--space-3)}.ct-autocomplete__status{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);border:0}@media(prefers-reduced-motion:reduce){.ct-autocomplete__listbox{transition:none}.ct-autocomplete__spinner{animation-duration:1.5s}}\n"] }]
|
|
8466
|
+
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], minChars: [{ type: i0.Input, args: [{ isSignal: true, alias: "minChars", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], loadingText: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingText", required: false }] }], openOnFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "openOnFocus", required: false }] }], hideOnEmpty: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideOnEmpty", required: false }] }], clearQueryOnSelect: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearQueryOnSelect", required: false }] }], showSearchIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSearchIcon", required: false }] }], showClear: [{ type: i0.Input, args: [{ isSignal: true, alias: "showClear", required: false }] }], clearAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearAriaLabel", required: false }] }], groupLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupLabels", required: false }] }], groupOrder: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupOrder", required: false }] }], autocompleteId: [{ type: i0.Input, args: [{ isSignal: true, alias: "autocompleteId", required: false }] }], query: [{ type: i0.Input, args: [{ isSignal: true, alias: "query", required: false }] }, { type: i0.Output, args: ["queryChange"] }], optionSelected: [{ type: i0.Output, args: ["optionSelected"] }], optionTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => AfAutocompleteOptionDirective), { isSignal: true }] }], inputRef: [{ type: i0.ViewChild, args: ['inputEl', { isSignal: true }] }] } });
|
|
8467
|
+
|
|
7793
8468
|
/**
|
|
7794
8469
|
* File upload component with drag-and-drop, validation, and form control support.
|
|
7795
8470
|
*
|
|
@@ -13626,5 +14301,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
|
|
|
13626
14301
|
* Generated bundle index. Do not edit.
|
|
13627
14302
|
*/
|
|
13628
14303
|
|
|
13629
|
-
export { AF_ACCORDION_I18N, AF_ALERT_I18N, AF_CHART_I18N, AF_CHART_PALETTE_SIZE, AF_INPUT_I18N, AF_SELECT_I18N, AF_SELECT_MENU_I18N, AF_TREE_I18N, AVATAR_SEED_PALETTE_SIZE, AfAccordionComponent, AfAccordionHarness, AfAccordionItemComponent, AfAccordionItemHarness, AfAlertComponent, AfAlertHarness, AfAppShellComponent, AfAppShellPageHeaderComponent, AfAppShellV2Component, AfAppShellV2ToolbarComponent, AfAvatarComponent, AfBadgeComponent, AfBadgeHarness, AfBannerComponent, AfBarChartComponent, AfBarChartHarness, AfBreadcrumbsComponent, AfButtonComponent, AfButtonHarness, AfCardComponent, AfCellDefDirective, AfChartDataTableComponent, AfCheckboxComponent, AfChipComponent, AfChipInputComponent, AfComboboxComponent, AfDataTableComponent, AfDatepickerComponent, AfDividerComponent, AfDonutChartComponent, AfDonutChartHarness, AfDrawerComponent, AfDropdownComponent, AfEmptyStateComponent, AfFieldComponent, AfFileUploadComponent, AfFormatLabelPipe, AfGaugeComponent, AfGaugeHarness, AfIconComponent, AfInputComponent, AfInputHarness, AfLineChartComponent, AfLineChartHarness, AfModalComponent, AfNavItemComponent, AfNavTabsComponent, AfNavbarComponent, AfPaginationComponent, AfPopoverComponent, AfPopoverTriggerDirective, AfProgressBarComponent, AfRadioComponent, AfRadioGroupComponent, AfSelectComponent, AfSelectHarness, AfSelectMenuComponent, AfSelectMenuHarness, AfSidebarComponent, AfSkeletonComponent, AfSkipLinkComponent, AfSliderComponent, AfSparklineComponent, AfSparklineHarness, AfSpinnerComponent, AfSwitchComponent, AfTabPanelComponent, AfTableBodyComponent, AfTableCellComponent, AfTableComponent, AfTableHeaderCellComponent, AfTableHeaderComponent, AfTableRowComponent, AfTabsComponent, AfTextareaComponent, AfToastContainerComponent, AfToastService, AfToggleGroupComponent, AfToolbarComponent, AfTooltipDirective, AfTreeComponent, AfTreeHarness, AfTreeNodeHarness };
|
|
14304
|
+
export { AF_ACCORDION_I18N, AF_ALERT_I18N, AF_CHART_I18N, AF_CHART_PALETTE_SIZE, AF_INPUT_I18N, AF_SELECT_I18N, AF_SELECT_MENU_I18N, AF_TREE_I18N, AVATAR_SEED_PALETTE_SIZE, AfAccordionComponent, AfAccordionHarness, AfAccordionItemComponent, AfAccordionItemHarness, AfAlertComponent, AfAlertHarness, AfAppShellComponent, AfAppShellPageHeaderComponent, AfAppShellV2Component, AfAppShellV2ToolbarComponent, AfAutocompleteComponent, AfAutocompleteOptionDirective, AfAvatarComponent, AfBadgeComponent, AfBadgeHarness, AfBannerComponent, AfBarChartComponent, AfBarChartHarness, AfBreadcrumbsComponent, AfButtonComponent, AfButtonHarness, AfCardComponent, AfCellDefDirective, AfChartDataTableComponent, AfCheckboxComponent, AfChipComponent, AfChipInputComponent, AfComboboxComponent, AfDataTableComponent, AfDatepickerComponent, AfDividerComponent, AfDonutChartComponent, AfDonutChartHarness, AfDrawerComponent, AfDropdownComponent, AfEmptyStateComponent, AfFieldComponent, AfFileUploadComponent, AfFormatLabelPipe, AfGaugeComponent, AfGaugeHarness, AfIconComponent, AfInputComponent, AfInputHarness, AfLineChartComponent, AfLineChartHarness, AfModalComponent, AfNavItemComponent, AfNavTabsComponent, AfNavbarComponent, AfPaginationComponent, AfPopoverComponent, AfPopoverTriggerDirective, AfProgressBarComponent, AfRadioComponent, AfRadioGroupComponent, AfSelectComponent, AfSelectHarness, AfSelectMenuComponent, AfSelectMenuHarness, AfSidebarComponent, AfSkeletonComponent, AfSkipLinkComponent, AfSliderComponent, AfSparklineComponent, AfSparklineHarness, AfSpinnerComponent, AfSwitchComponent, AfTabPanelComponent, AfTableBodyComponent, AfTableCellComponent, AfTableComponent, AfTableHeaderCellComponent, AfTableHeaderComponent, AfTableRowComponent, AfTabsComponent, AfTextareaComponent, AfToastContainerComponent, AfToastService, AfToggleGroupComponent, AfToolbarComponent, AfTooltipDirective, AfTreeComponent, AfTreeHarness, AfTreeNodeHarness };
|
|
13630
14305
|
//# sourceMappingURL=neuravision-ng-construct.mjs.map
|