codexly-ui 0.22.1 → 0.23.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.
@@ -19732,9 +19732,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
19732
19732
  let _clxSearchIdCounter = 0;
19733
19733
  /** Storefront search bar — same color/size/CVA conventions as ClxInputComponent, but styled as
19734
19734
  * one continuous pill (input fused with a solid search button) rather than a bordered field with
19735
- * a detached button, matching how e-commerce header search boxes are typically laid out. */
19735
+ * a detached button, matching how e-commerce header search boxes are typically laid out.
19736
+ *
19737
+ * Optional autocomplete: the host listens to (queryChange) — the text, debounced, once it has
19738
+ * `minChars` — and feeds [suggestions] back (product rows with image and price, shortcuts, past
19739
+ * searches, a "¿Quisiste decir…?" correction, the result total). The panel opens under the bar,
19740
+ * supports ↑ ↓ Enter Esc, and reports picks through (suggestionSelect); Enter without a picked row,
19741
+ * the button, the correction and "Ver todos" emit (search). With [openOnFocus] the panel also shows
19742
+ * for an empty box (e.g. recent searches). */
19736
19743
  class ClxSearchComponent {
19737
19744
  _themeSvc = inject(ClxThemeService);
19745
+ _el = inject((ElementRef));
19746
+ _sso = inject(ScrollStrategyOptions);
19738
19747
  color = input(undefined, ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
19739
19748
  _color = computed(() => this.color() ?? this._themeSvc.config().primaryColor, ...(ngDevMode ? [{ debugName: "_color" }] : /* istanbul ignore next */ []));
19740
19749
  /** Overrides only the search button's text color — see ClxButtonComponent's textColor input. */
@@ -19747,13 +19756,45 @@ class ClxSearchComponent {
19747
19756
  value = input('', ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
19748
19757
  /** Disabled state for standalone use (without FormControl) */
19749
19758
  disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
19759
+ /** Autocomplete content for the current text; null = no panel. */
19760
+ suggestions = input(null, ...(ngDevMode ? [{ debugName: "suggestions" }] : /* istanbul ignore next */ []));
19761
+ /** The host is fetching suggestions. */
19762
+ loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
19763
+ /** (queryChange) waits this long after the last keystroke. */
19764
+ debounceMs = input(250, ...(ngDevMode ? [{ debugName: "debounceMs" }] : /* istanbul ignore next */ []));
19765
+ /** (queryChange) only from this many characters (0 = also for an empty box). */
19766
+ minChars = input(2, ...(ngDevMode ? [{ debugName: "minChars" }] : /* istanbul ignore next */ []));
19767
+ /** Show the panel when focusing an empty box (recent searches…). */
19768
+ openOnFocus = input(false, ...(ngDevMode ? [{ debugName: "openOnFocus" }] : /* istanbul ignore next */ []));
19750
19769
  search = output();
19770
+ /** Debounced text for the host to fetch suggestions for ('' when cleared). */
19771
+ queryChange = output();
19772
+ suggestionSelect = output();
19751
19773
  _inputId = `clx-search-${++_clxSearchIdCounter}`;
19752
19774
  _value = signal('', ...(ngDevMode ? [{ debugName: "_value" }] : /* istanbul ignore next */ []));
19753
19775
  _cvaDisabled = signal(false, ...(ngDevMode ? [{ debugName: "_cvaDisabled" }] : /* istanbul ignore next */ []));
19754
19776
  _cvaConnected = false;
19755
19777
  _cardDisabled = inject(CLX_CARD_DISABLED_CONTEXT, { optional: true });
19778
+ _focused = signal(false, ...(ngDevMode ? [{ debugName: "_focused" }] : /* istanbul ignore next */ []));
19779
+ _dismissed = signal(false, ...(ngDevMode ? [{ debugName: "_dismissed" }] : /* istanbul ignore next */ []));
19780
+ _active = signal(null, ...(ngDevMode ? [{ debugName: "_active" }] : /* istanbul ignore next */ []));
19781
+ _panelWidth = signal(0, ...(ngDevMode ? [{ debugName: "_panelWidth" }] : /* istanbul ignore next */ []));
19782
+ _debounce;
19756
19783
  _disabled = computed(() => this._cvaDisabled() || this.disabled() || !!this._cardDisabled?.(), ...(ngDevMode ? [{ debugName: "_disabled" }] : /* istanbul ignore next */ []));
19784
+ _scrollStrategy = this._sso.reposition();
19785
+ _positions = [
19786
+ { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 6 },
19787
+ ];
19788
+ /** Flat list of the panel's rows, for ↑ ↓. */
19789
+ _rows = computed(() => (this.suggestions()?.groups ?? [])
19790
+ .flatMap(g => g.items.map(item => ({ group: g.key, item }))), ...(ngDevMode ? [{ debugName: "_rows" }] : /* istanbul ignore next */ []));
19791
+ _panelOpen = computed(() => {
19792
+ if (!this._focused() || this._dismissed() || this._disabled())
19793
+ return false;
19794
+ const s = this.suggestions();
19795
+ const hasContent = !!s && (!!s.correction || s.groups.some(g => g.items.length > 0));
19796
+ return hasContent && (this._value().trim().length > 0 || this.openOnFocus());
19797
+ }, ...(ngDevMode ? [{ debugName: "_panelOpen" }] : /* istanbul ignore next */ []));
19757
19798
  _valueEffect = effect(() => {
19758
19799
  const v = this.value();
19759
19800
  if (!this._cvaConnected)
@@ -19761,6 +19802,9 @@ class ClxSearchComponent {
19761
19802
  }, ...(ngDevMode ? [{ debugName: "_valueEffect" }] : /* istanbul ignore next */ []));
19762
19803
  _onChange = () => { };
19763
19804
  _onTouched = () => { };
19805
+ constructor() {
19806
+ inject(DestroyRef).onDestroy(() => clearTimeout(this._debounce));
19807
+ }
19764
19808
  _sizeConfig = computed(() => INPUT_SIZE_MAP[this._size()], ...(ngDevMode ? [{ debugName: "_sizeConfig" }] : /* istanbul ignore next */ []));
19765
19809
  _buttonSize = computed(() => {
19766
19810
  switch (this._size()) {
@@ -19769,6 +19813,7 @@ class ClxSearchComponent {
19769
19813
  default: return 'md';
19770
19814
  }
19771
19815
  }, ...(ngDevMode ? [{ debugName: "_buttonSize" }] : /* istanbul ignore next */ []));
19816
+ _accentText = computed(() => resolveColor(this._color()).textEmphasis, ...(ngDevMode ? [{ debugName: "_accentText" }] : /* istanbul ignore next */ []));
19772
19817
  _wrapperClass = computed(() => {
19773
19818
  const focusCls = this._disabled() ? '' : resolveColor(this._color()).focusWithin;
19774
19819
  return `relative flex items-stretch ${resolveRadius(this._themeSvc.config().borderRadius)} border border-clx-border ${focusCls} overflow-hidden transition-[border-color,box-shadow] duration-200`;
@@ -19797,34 +19842,106 @@ class ClxSearchComponent {
19797
19842
  const val = event.target.value;
19798
19843
  this._value.set(val);
19799
19844
  this._onChange(val);
19845
+ this._dismissed.set(false);
19846
+ this._active.set(null);
19847
+ this._emitQuery(val);
19848
+ }
19849
+ _handleFocus() {
19850
+ this._panelWidth.set(Math.round(this._el.nativeElement.getBoundingClientRect().width));
19851
+ this._focused.set(true);
19852
+ this._dismissed.set(false);
19853
+ if (this.openOnFocus() && !this._value().trim())
19854
+ this.queryChange.emit('');
19800
19855
  }
19801
19856
  _handleBlur() {
19802
19857
  this._onTouched();
19858
+ this._focused.set(false);
19859
+ }
19860
+ _handleKeydown(event) {
19861
+ const rows = this._rows();
19862
+ if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
19863
+ if (!this._panelOpen() || !rows.length)
19864
+ return;
19865
+ event.preventDefault();
19866
+ const current = this._active();
19867
+ const index = current ? rows.findIndex(r => r.group === current.group && r.item.id === current.id) : -1;
19868
+ const next = event.key === 'ArrowDown'
19869
+ ? (index + 1) % rows.length
19870
+ : (index <= 0 ? rows.length - 1 : index - 1);
19871
+ this._active.set({ group: rows[next].group, id: rows[next].item.id });
19872
+ }
19873
+ else if (event.key === 'Enter') {
19874
+ event.preventDefault();
19875
+ const current = this._active();
19876
+ const row = current && this._panelOpen()
19877
+ ? rows.find(r => r.group === current.group && r.item.id === current.id)
19878
+ : undefined;
19879
+ if (row)
19880
+ this._select(row.group, row.item);
19881
+ else
19882
+ this._handleSubmit();
19883
+ }
19884
+ else if (event.key === 'Escape') {
19885
+ this._close();
19886
+ }
19887
+ }
19888
+ _isActive(group, item) {
19889
+ const a = this._active();
19890
+ return !!a && a.group === group && a.id === item.id;
19891
+ }
19892
+ _activate(group, item) {
19893
+ this._active.set({ group, id: item.id });
19894
+ }
19895
+ _select(group, item) {
19896
+ this._close();
19897
+ this.suggestionSelect.emit({ group, item });
19898
+ }
19899
+ _searchFor(text) {
19900
+ this._value.set(text);
19901
+ this._onChange(text);
19902
+ this._handleSubmit();
19903
+ }
19904
+ _close() {
19905
+ this._dismissed.set(true);
19906
+ this._active.set(null);
19803
19907
  }
19804
19908
  _handleSubmit() {
19805
19909
  if (this._disabled())
19806
19910
  return;
19911
+ this._close();
19912
+ clearTimeout(this._debounce);
19807
19913
  this.search.emit(this._value());
19808
19914
  }
19915
+ _emitQuery(val) {
19916
+ clearTimeout(this._debounce);
19917
+ const text = val.trim();
19918
+ if (text.length > 0 && text.length < this.minChars())
19919
+ return;
19920
+ this._debounce = setTimeout(() => this.queryChange.emit(text), text ? this.debounceMs() : 0);
19921
+ }
19809
19922
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ClxSearchComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
19810
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.15", type: ClxSearchComponent, isStandalone: true, selector: "clx-search", inputs: { color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, buttonTextColor: { classPropertyName: "buttonTextColor", publicName: "buttonTextColor", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, buttonLabel: { classPropertyName: "buttonLabel", publicName: "buttonLabel", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { search: "search" }, host: { classAttribute: "block w-full" }, providers: [
19923
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: ClxSearchComponent, isStandalone: true, selector: "clx-search", inputs: { color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, buttonTextColor: { classPropertyName: "buttonTextColor", publicName: "buttonTextColor", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, buttonLabel: { classPropertyName: "buttonLabel", publicName: "buttonLabel", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, suggestions: { classPropertyName: "suggestions", publicName: "suggestions", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, debounceMs: { classPropertyName: "debounceMs", publicName: "debounceMs", isSignal: true, isRequired: false, transformFunction: null }, minChars: { classPropertyName: "minChars", publicName: "minChars", isSignal: true, isRequired: false, transformFunction: null }, openOnFocus: { classPropertyName: "openOnFocus", publicName: "openOnFocus", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { search: "search", queryChange: "queryChange", suggestionSelect: "suggestionSelect" }, host: { classAttribute: "block w-full" }, providers: [
19811
19924
  {
19812
19925
  provide: NG_VALUE_ACCESSOR,
19813
19926
  useExisting: forwardRef(() => ClxSearchComponent),
19814
19927
  multi: true,
19815
19928
  },
19816
19929
  ], ngImport: i0, template: `
19817
- <div [class]="_wrapperClass()">
19930
+ <div [class]="_wrapperClass()" cdkOverlayOrigin #origin="cdkOverlayOrigin">
19818
19931
  <input
19819
19932
  [id]="_inputId"
19820
19933
  type="search"
19934
+ role="combobox"
19935
+ [attr.aria-expanded]="_panelOpen()"
19936
+ [attr.aria-controls]="_inputId + '-panel'"
19821
19937
  [placeholder]="placeholder()"
19822
19938
  [disabled]="_disabled()"
19823
19939
  [value]="_value()"
19824
19940
  [class]="_inputClass()"
19825
19941
  autocomplete="off"
19826
19942
  (input)="_handleInput($event)"
19827
- (keydown.enter)="_handleSubmit()"
19943
+ (focus)="_handleFocus()"
19944
+ (keydown)="_handleKeydown($event)"
19828
19945
  (blur)="_handleBlur()"
19829
19946
  />
19830
19947
  <button
@@ -19841,14 +19958,77 @@ class ClxSearchComponent {
19841
19958
  {{ buttonLabel() }}
19842
19959
  </button>
19843
19960
  </div>
19844
- `, isInline: true, dependencies: [{ kind: "component", type: ClxButtonComponent, selector: "button[clx-button], a[clx-button]", inputs: ["variant", "color", "emphasis", "textColor", "size", "shape", "loading", "disabled", "block", "icon", "iconPosition", "iconOnly", "badge", "badgeColor"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
19961
+
19962
+ <ng-template
19963
+ cdkConnectedOverlay
19964
+ [cdkConnectedOverlayOrigin]="origin"
19965
+ [cdkConnectedOverlayOpen]="_panelOpen()"
19966
+ [cdkConnectedOverlayPositions]="_positions"
19967
+ [cdkConnectedOverlayScrollStrategy]="_scrollStrategy"
19968
+ [cdkConnectedOverlayWidth]="_panelWidth()"
19969
+ [cdkConnectedOverlayPush]="true"
19970
+ (overlayOutsideClick)="_close()"
19971
+ (detach)="_close()">
19972
+ <div [id]="_inputId + '-panel'" role="listbox"
19973
+ class="w-full max-h-[70vh] overflow-y-auto bg-clx-surface rounded-xl shadow-xl border border-clx-border py-2"
19974
+ (mousedown)="$event.preventDefault()">
19975
+ @if (loading()) {
19976
+ <div class="flex items-center gap-2 px-4 py-2 text-xs text-clx-text-subtle">
19977
+ <clx-spinner size="xs" [color]="_color()" /> Buscando…
19978
+ </div>
19979
+ }
19980
+ @if (suggestions()?.correction; as correction) {
19981
+ <button type="button" class="w-full text-left px-4 py-2 text-sm text-clx-text-muted hover:bg-clx-surface-2"
19982
+ (click)="_searchFor(correction)">
19983
+ ¿Quisiste decir <span class="font-semibold" [class]="_accentText()">{{ correction }}</span>?
19984
+ </button>
19985
+ }
19986
+ @for (group of suggestions()?.groups ?? []; track group.key) {
19987
+ @if (group.items.length) {
19988
+ <p class="px-4 pt-2 pb-1 text-[11px] font-semibold uppercase tracking-wide text-clx-text-subtle">{{ group.title }}</p>
19989
+ @for (item of group.items; track item.id) {
19990
+ <button type="button" role="option"
19991
+ [attr.aria-selected]="_isActive(group.key, item)"
19992
+ class="w-full flex items-center gap-3 px-4 py-2 text-left transition-colors"
19993
+ [class]="_isActive(group.key, item) ? 'bg-clx-surface-2' : 'hover:bg-clx-surface-2'"
19994
+ (mouseenter)="_activate(group.key, item)"
19995
+ (click)="_select(group.key, item)">
19996
+ @if (item.image) {
19997
+ <img [src]="item.image" [alt]="item.label" class="w-10 h-10 rounded-lg object-cover shrink-0 bg-clx-surface-2" />
19998
+ } @else if (item.icon) {
19999
+ <span class="w-8 h-8 rounded-lg bg-clx-surface-2 flex items-center justify-center shrink-0">
20000
+ <span clx-icon [name]="item.icon" size="sm" class="text-clx-text-muted"></span>
20001
+ </span>
20002
+ }
20003
+ <span class="flex-1 min-w-0">
20004
+ <span class="block text-sm text-clx-text-label truncate">{{ item.label }}</span>
20005
+ @if (item.sublabel) {
20006
+ <span class="block text-xs text-clx-text-subtle truncate">{{ item.sublabel }}</span>
20007
+ }
20008
+ </span>
20009
+ @if (item.meta) {
20010
+ <span class="text-sm font-semibold text-clx-text-label shrink-0">{{ item.meta }}</span>
20011
+ }
20012
+ </button>
20013
+ }
20014
+ }
20015
+ }
20016
+ @if (_value().trim() && suggestions()?.total) {
20017
+ <button type="button" class="w-full text-left px-4 pt-3 pb-1 text-sm font-semibold" [class]="_accentText()"
20018
+ (click)="_handleSubmit()">
20019
+ Ver los {{ suggestions()!.total }} resultados de “{{ _value().trim() }}”
20020
+ </button>
20021
+ }
20022
+ </div>
20023
+ </ng-template>
20024
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i1.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i1.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: ClxButtonComponent, selector: "button[clx-button], a[clx-button]", inputs: ["variant", "color", "emphasis", "textColor", "size", "shape", "loading", "disabled", "block", "icon", "iconPosition", "iconOnly", "badge", "badgeColor"] }, { kind: "component", type: ClxIconComponent, selector: "span[clx-icon]", inputs: ["name", "size", "color", "fill"] }, { kind: "component", type: ClxSpinnerComponent, selector: "clx-spinner", inputs: ["size", "color", "variant"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
19845
20025
  }
19846
20026
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ClxSearchComponent, decorators: [{
19847
20027
  type: Component,
19848
20028
  args: [{
19849
20029
  selector: 'clx-search',
19850
20030
  standalone: true,
19851
- imports: [ClxButtonComponent],
20031
+ imports: [OverlayModule, ClxButtonComponent, ClxIconComponent, ClxSpinnerComponent],
19852
20032
  providers: [
19853
20033
  {
19854
20034
  provide: NG_VALUE_ACCESSOR,
@@ -19857,17 +20037,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
19857
20037
  },
19858
20038
  ],
19859
20039
  template: `
19860
- <div [class]="_wrapperClass()">
20040
+ <div [class]="_wrapperClass()" cdkOverlayOrigin #origin="cdkOverlayOrigin">
19861
20041
  <input
19862
20042
  [id]="_inputId"
19863
20043
  type="search"
20044
+ role="combobox"
20045
+ [attr.aria-expanded]="_panelOpen()"
20046
+ [attr.aria-controls]="_inputId + '-panel'"
19864
20047
  [placeholder]="placeholder()"
19865
20048
  [disabled]="_disabled()"
19866
20049
  [value]="_value()"
19867
20050
  [class]="_inputClass()"
19868
20051
  autocomplete="off"
19869
20052
  (input)="_handleInput($event)"
19870
- (keydown.enter)="_handleSubmit()"
20053
+ (focus)="_handleFocus()"
20054
+ (keydown)="_handleKeydown($event)"
19871
20055
  (blur)="_handleBlur()"
19872
20056
  />
19873
20057
  <button
@@ -19884,6 +20068,69 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
19884
20068
  {{ buttonLabel() }}
19885
20069
  </button>
19886
20070
  </div>
20071
+
20072
+ <ng-template
20073
+ cdkConnectedOverlay
20074
+ [cdkConnectedOverlayOrigin]="origin"
20075
+ [cdkConnectedOverlayOpen]="_panelOpen()"
20076
+ [cdkConnectedOverlayPositions]="_positions"
20077
+ [cdkConnectedOverlayScrollStrategy]="_scrollStrategy"
20078
+ [cdkConnectedOverlayWidth]="_panelWidth()"
20079
+ [cdkConnectedOverlayPush]="true"
20080
+ (overlayOutsideClick)="_close()"
20081
+ (detach)="_close()">
20082
+ <div [id]="_inputId + '-panel'" role="listbox"
20083
+ class="w-full max-h-[70vh] overflow-y-auto bg-clx-surface rounded-xl shadow-xl border border-clx-border py-2"
20084
+ (mousedown)="$event.preventDefault()">
20085
+ @if (loading()) {
20086
+ <div class="flex items-center gap-2 px-4 py-2 text-xs text-clx-text-subtle">
20087
+ <clx-spinner size="xs" [color]="_color()" /> Buscando…
20088
+ </div>
20089
+ }
20090
+ @if (suggestions()?.correction; as correction) {
20091
+ <button type="button" class="w-full text-left px-4 py-2 text-sm text-clx-text-muted hover:bg-clx-surface-2"
20092
+ (click)="_searchFor(correction)">
20093
+ ¿Quisiste decir <span class="font-semibold" [class]="_accentText()">{{ correction }}</span>?
20094
+ </button>
20095
+ }
20096
+ @for (group of suggestions()?.groups ?? []; track group.key) {
20097
+ @if (group.items.length) {
20098
+ <p class="px-4 pt-2 pb-1 text-[11px] font-semibold uppercase tracking-wide text-clx-text-subtle">{{ group.title }}</p>
20099
+ @for (item of group.items; track item.id) {
20100
+ <button type="button" role="option"
20101
+ [attr.aria-selected]="_isActive(group.key, item)"
20102
+ class="w-full flex items-center gap-3 px-4 py-2 text-left transition-colors"
20103
+ [class]="_isActive(group.key, item) ? 'bg-clx-surface-2' : 'hover:bg-clx-surface-2'"
20104
+ (mouseenter)="_activate(group.key, item)"
20105
+ (click)="_select(group.key, item)">
20106
+ @if (item.image) {
20107
+ <img [src]="item.image" [alt]="item.label" class="w-10 h-10 rounded-lg object-cover shrink-0 bg-clx-surface-2" />
20108
+ } @else if (item.icon) {
20109
+ <span class="w-8 h-8 rounded-lg bg-clx-surface-2 flex items-center justify-center shrink-0">
20110
+ <span clx-icon [name]="item.icon" size="sm" class="text-clx-text-muted"></span>
20111
+ </span>
20112
+ }
20113
+ <span class="flex-1 min-w-0">
20114
+ <span class="block text-sm text-clx-text-label truncate">{{ item.label }}</span>
20115
+ @if (item.sublabel) {
20116
+ <span class="block text-xs text-clx-text-subtle truncate">{{ item.sublabel }}</span>
20117
+ }
20118
+ </span>
20119
+ @if (item.meta) {
20120
+ <span class="text-sm font-semibold text-clx-text-label shrink-0">{{ item.meta }}</span>
20121
+ }
20122
+ </button>
20123
+ }
20124
+ }
20125
+ }
20126
+ @if (_value().trim() && suggestions()?.total) {
20127
+ <button type="button" class="w-full text-left px-4 pt-3 pb-1 text-sm font-semibold" [class]="_accentText()"
20128
+ (click)="_handleSubmit()">
20129
+ Ver los {{ suggestions()!.total }} resultados de “{{ _value().trim() }}”
20130
+ </button>
20131
+ }
20132
+ </div>
20133
+ </ng-template>
19887
20134
  `,
19888
20135
  encapsulation: ViewEncapsulation.None,
19889
20136
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -19891,7 +20138,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
19891
20138
  class: 'block w-full',
19892
20139
  },
19893
20140
  }]
19894
- }], propDecorators: { color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], buttonTextColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "buttonTextColor", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], buttonLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "buttonLabel", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], search: [{ type: i0.Output, args: ["search"] }] } });
20141
+ }], ctorParameters: () => [], propDecorators: { color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], buttonTextColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "buttonTextColor", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], buttonLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "buttonLabel", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], suggestions: [{ type: i0.Input, args: [{ isSignal: true, alias: "suggestions", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], debounceMs: [{ type: i0.Input, args: [{ isSignal: true, alias: "debounceMs", required: false }] }], minChars: [{ type: i0.Input, args: [{ isSignal: true, alias: "minChars", required: false }] }], openOnFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "openOnFocus", required: false }] }], search: [{ type: i0.Output, args: ["search"] }], queryChange: [{ type: i0.Output, args: ["queryChange"] }], suggestionSelect: [{ type: i0.Output, args: ["suggestionSelect"] }] } });
19895
20142
 
19896
20143
  class ClxAnimateGroupDirective {
19897
20144
  stagger = input(100, { ...(ngDevMode ? { debugName: "stagger" } : /* istanbul ignore next */ {}), transform: numberAttribute });