codexly-ui 0.22.0 → 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.
@@ -5746,6 +5746,8 @@ class ClxSelectComponent {
5746
5746
  hint = input('', ...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
5747
5747
  errorMessage = input('Este campo es requerido', ...(ngDevMode ? [{ debugName: "errorMessage" }] : /* istanbul ignore next */ []));
5748
5748
  disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
5749
+ /** Multiple mode: "Seleccionar todos" / "Quitar todos" on top of the options. */
5750
+ selectAll = input(true, ...(ngDevMode ? [{ debugName: "selectAll" }] : /* istanbul ignore next */ []));
5749
5751
  // ── Async / autocomplete ────────────────────────────────────────────────────
5750
5752
  async = input(false, ...(ngDevMode ? [{ debugName: "async" }] : /* istanbul ignore next */ []));
5751
5753
  loadOptions = input(null, ...(ngDevMode ? [{ debugName: "loadOptions" }] : /* istanbul ignore next */ []));
@@ -5957,6 +5959,21 @@ class ClxSelectComponent {
5957
5959
  this._closePanel();
5958
5960
  }
5959
5961
  }
5962
+ _selectableFiltered = computed(() => this._filteredOptions().filter(o => !o.disabled), ...(ngDevMode ? [{ debugName: "_selectableFiltered" }] : /* istanbul ignore next */ []));
5963
+ _allFilteredSelected = computed(() => {
5964
+ const selected = this._selectedVals();
5965
+ return this._selectableFiltered().every(o => selected.includes(o.value));
5966
+ }, ...(ngDevMode ? [{ debugName: "_allFilteredSelected" }] : /* istanbul ignore next */ []));
5967
+ /** Selects every option the search shows, or removes them all when they already are. */
5968
+ _toggleAll() {
5969
+ const values = this._selectableFiltered().map(o => o.value);
5970
+ const current = this._selectedVals();
5971
+ const next = this._allFilteredSelected()
5972
+ ? current.filter(v => !values.includes(v))
5973
+ : [...current, ...values.filter(v => !current.includes(v))];
5974
+ this._selectedVals.set(next);
5975
+ this._onChange(next);
5976
+ }
5960
5977
  _deselect(value) {
5961
5978
  const next = this._selectedVals().filter(v => v !== value);
5962
5979
  this._selectedVals.set(next);
@@ -6003,7 +6020,7 @@ class ClxSelectComponent {
6003
6020
  this._cvaDisabled.set(disabled);
6004
6021
  }
6005
6022
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ClxSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6006
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: ClxSelectComponent, isStandalone: true, selector: "clx-select", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, activeColor: { classPropertyName: "activeColor", publicName: "activeColor", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, errorMessage: { classPropertyName: "errorMessage", publicName: "errorMessage", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, async: { classPropertyName: "async", publicName: "async", isSignal: true, isRequired: false, transformFunction: null }, loadOptions: { classPropertyName: "loadOptions", publicName: "loadOptions", 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 } }, host: { listeners: { "keydown.escape": "onEscape()" }, classAttribute: "flex flex-col gap-1.5 w-full" }, ngImport: i0, template: `
6023
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: ClxSelectComponent, isStandalone: true, selector: "clx-select", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, activeColor: { classPropertyName: "activeColor", publicName: "activeColor", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, errorMessage: { classPropertyName: "errorMessage", publicName: "errorMessage", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, selectAll: { classPropertyName: "selectAll", publicName: "selectAll", isSignal: true, isRequired: false, transformFunction: null }, async: { classPropertyName: "async", publicName: "async", isSignal: true, isRequired: false, transformFunction: null }, loadOptions: { classPropertyName: "loadOptions", publicName: "loadOptions", 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 } }, host: { listeners: { "keydown.escape": "onEscape()" }, classAttribute: "flex flex-col gap-1.5 w-full" }, ngImport: i0, template: `
6007
6024
  @if (label()) {
6008
6025
  <span [id]="_labelId" [class]="_labelClass()">{{ label() }}</span>
6009
6026
  }
@@ -6118,6 +6135,17 @@ class ClxSelectComponent {
6118
6135
  } @else if (_filteredOptions().length === 0) {
6119
6136
  <div class="px-3 py-4 text-center text-sm text-clx-text-subtle">Sin resultados</div>
6120
6137
  } @else {
6138
+ <!-- "Seleccionar todos" over the options the search shows (multiple only) -->
6139
+ @if (multiple() && selectAll() && _selectableFiltered().length > 1) {
6140
+ <button
6141
+ type="button"
6142
+ class="w-full flex items-center gap-2 px-3 py-1.5 text-sm font-medium rounded-lg text-clx-text-muted hover:bg-clx-surface-2 cursor-pointer"
6143
+ (mousedown)="$event.preventDefault()"
6144
+ (click)="_toggleAll()">
6145
+ <span clx-icon [name]="_allFilteredSelected() ? 'remove_done' : 'done_all'" size="xs" class="shrink-0"></span>
6146
+ <span class="flex-1 text-left">{{ _allFilteredSelected() ? 'Quitar todos' : 'Seleccionar todos' }}</span>
6147
+ </button>
6148
+ }
6121
6149
  @for (opt of _filteredOptions(); track opt.value) {
6122
6150
  <button
6123
6151
  type="button"
@@ -6269,6 +6297,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
6269
6297
  } @else if (_filteredOptions().length === 0) {
6270
6298
  <div class="px-3 py-4 text-center text-sm text-clx-text-subtle">Sin resultados</div>
6271
6299
  } @else {
6300
+ <!-- "Seleccionar todos" over the options the search shows (multiple only) -->
6301
+ @if (multiple() && selectAll() && _selectableFiltered().length > 1) {
6302
+ <button
6303
+ type="button"
6304
+ class="w-full flex items-center gap-2 px-3 py-1.5 text-sm font-medium rounded-lg text-clx-text-muted hover:bg-clx-surface-2 cursor-pointer"
6305
+ (mousedown)="$event.preventDefault()"
6306
+ (click)="_toggleAll()">
6307
+ <span clx-icon [name]="_allFilteredSelected() ? 'remove_done' : 'done_all'" size="xs" class="shrink-0"></span>
6308
+ <span class="flex-1 text-left">{{ _allFilteredSelected() ? 'Quitar todos' : 'Seleccionar todos' }}</span>
6309
+ </button>
6310
+ }
6272
6311
  @for (opt of _filteredOptions(); track opt.value) {
6273
6312
  <button
6274
6313
  type="button"
@@ -6302,7 +6341,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
6302
6341
  changeDetection: ChangeDetectionStrategy.OnPush,
6303
6342
  host: { class: 'flex flex-col gap-1.5 w-full' },
6304
6343
  }]
6305
- }], ctorParameters: () => [], 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 }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], activeColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeColor", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], errorMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessage", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], async: [{ type: i0.Input, args: [{ isSignal: true, alias: "async", required: false }] }], loadOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadOptions", required: false }] }], debounceMs: [{ type: i0.Input, args: [{ isSignal: true, alias: "debounceMs", required: false }] }], minChars: [{ type: i0.Input, args: [{ isSignal: true, alias: "minChars", required: false }] }], onEscape: [{
6344
+ }], ctorParameters: () => [], 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 }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], activeColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeColor", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], errorMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessage", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], selectAll: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectAll", required: false }] }], async: [{ type: i0.Input, args: [{ isSignal: true, alias: "async", required: false }] }], loadOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadOptions", required: false }] }], debounceMs: [{ type: i0.Input, args: [{ isSignal: true, alias: "debounceMs", required: false }] }], minChars: [{ type: i0.Input, args: [{ isSignal: true, alias: "minChars", required: false }] }], onEscape: [{
6306
6345
  type: HostListener,
6307
6346
  args: ['keydown.escape']
6308
6347
  }] } });
@@ -10978,7 +11017,7 @@ class ClxEditorComponent {
10978
11017
  } @else if (hint()) {
10979
11018
  <p [class]="_sizeCfg().hint + ' text-gray-400'">{{ hint() }}</p>
10980
11019
  }
10981
- `, isInline: true, styles: [".clx-editor-body:empty:before{content:attr(data-placeholder);color:#9ca3af;pointer-events:none}.clx-editor-body:focus{outline:none}.clx-editor-body p{margin:0 0 .4em}.clx-editor-body p:last-child{margin-bottom:0}.clx-editor-body h1{font-size:1.5em;font-weight:700;margin:.5em 0}.clx-editor-body h2{font-size:1.25em;font-weight:600;margin:.5em 0}.clx-editor-body h3{font-size:1.1em;font-weight:600;margin:.5em 0}.clx-editor-body ul,.clx-editor-body ol{padding-left:1.5em;margin:.35em 0}.clx-editor-body li{margin:.1em 0}.clx-editor-body blockquote{border-left:3px solid #e2e8f0;padding-left:.75em;color:#64748b;margin:.4em 0}.clx-editor-body code{background:#f1f5f9;border-radius:3px;padding:.1em .3em;font-size:.875em;font-family:ui-monospace,monospace}.clx-editor-body a{color:#6366f1;text-decoration:underline}.clx-editor-body hr{border:none;border-top:1px solid #e2e8f0;margin:.6em 0}.clx-editor-color-btn{position:relative;overflow:hidden}.clx-editor-color-btn input[type=color]{position:absolute;inset:0;opacity:0;width:100%;height:100%;cursor:pointer;border:none;padding:0}.clx-editor-toolbar-select clx-select .flex.flex-col{gap:0!important}.clx-editor-source{width:100%;resize:none;border:none;outline:none;background:#0f172a;color:#e2e8f0;font-family:ui-monospace,Cascadia Code,Fira Code,monospace;font-size:.8125rem;line-height:1.6;padding:1rem;tab-size:2}\n"], dependencies: [{ kind: "component", type: ClxIconComponent, selector: "span[clx-icon]", inputs: ["name", "size", "color", "fill"] }, { 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: ClxSelectComponent, selector: "clx-select", inputs: ["label", "placeholder", "options", "multiple", "searchable", "searchPlaceholder", "activeColor", "size", "hint", "errorMessage", "disabled", "async", "loadOptions", "debounceMs", "minChars"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
11020
+ `, isInline: true, styles: [".clx-editor-body:empty:before{content:attr(data-placeholder);color:#9ca3af;pointer-events:none}.clx-editor-body:focus{outline:none}.clx-editor-body p{margin:0 0 .4em}.clx-editor-body p:last-child{margin-bottom:0}.clx-editor-body h1{font-size:1.5em;font-weight:700;margin:.5em 0}.clx-editor-body h2{font-size:1.25em;font-weight:600;margin:.5em 0}.clx-editor-body h3{font-size:1.1em;font-weight:600;margin:.5em 0}.clx-editor-body ul,.clx-editor-body ol{padding-left:1.5em;margin:.35em 0}.clx-editor-body li{margin:.1em 0}.clx-editor-body blockquote{border-left:3px solid #e2e8f0;padding-left:.75em;color:#64748b;margin:.4em 0}.clx-editor-body code{background:#f1f5f9;border-radius:3px;padding:.1em .3em;font-size:.875em;font-family:ui-monospace,monospace}.clx-editor-body a{color:#6366f1;text-decoration:underline}.clx-editor-body hr{border:none;border-top:1px solid #e2e8f0;margin:.6em 0}.clx-editor-color-btn{position:relative;overflow:hidden}.clx-editor-color-btn input[type=color]{position:absolute;inset:0;opacity:0;width:100%;height:100%;cursor:pointer;border:none;padding:0}.clx-editor-toolbar-select clx-select .flex.flex-col{gap:0!important}.clx-editor-source{width:100%;resize:none;border:none;outline:none;background:#0f172a;color:#e2e8f0;font-family:ui-monospace,Cascadia Code,Fira Code,monospace;font-size:.8125rem;line-height:1.6;padding:1rem;tab-size:2}\n"], dependencies: [{ kind: "component", type: ClxIconComponent, selector: "span[clx-icon]", inputs: ["name", "size", "color", "fill"] }, { 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: ClxSelectComponent, selector: "clx-select", inputs: ["label", "placeholder", "options", "multiple", "searchable", "searchPlaceholder", "activeColor", "size", "hint", "errorMessage", "disabled", "selectAll", "async", "loadOptions", "debounceMs", "minChars"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
10982
11021
  }
10983
11022
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ClxEditorComponent, decorators: [{
10984
11023
  type: Component,
@@ -14284,7 +14323,7 @@ class ClxPaginationComponent {
14284
14323
  }
14285
14324
 
14286
14325
  </div>
14287
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { 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: ClxSelectComponent, selector: "clx-select", inputs: ["label", "placeholder", "options", "multiple", "searchable", "searchPlaceholder", "activeColor", "size", "hint", "errorMessage", "disabled", "async", "loadOptions", "debounceMs", "minChars"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
14326
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { 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: ClxSelectComponent, selector: "clx-select", inputs: ["label", "placeholder", "options", "multiple", "searchable", "searchPlaceholder", "activeColor", "size", "hint", "errorMessage", "disabled", "selectAll", "async", "loadOptions", "debounceMs", "minChars"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
14288
14327
  }
14289
14328
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ClxPaginationComponent, decorators: [{
14290
14329
  type: Component,
@@ -14655,7 +14694,7 @@ class ClxFilterPanelComponent {
14655
14694
  </div>
14656
14695
  }
14657
14696
  }
14658
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: ClxIconComponent, selector: "span[clx-icon]", inputs: ["name", "size", "color", "fill"] }, { 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: ClxBadgeComponent, selector: "span[clx-badge]", inputs: ["variant", "color", "size", "shape", "positioned", "textColor"] }, { kind: "component", type: ClxCheckboxComponent, selector: "clx-checkbox", inputs: ["color", "variant", "size", "label", "value", "disabled"] }, { kind: "component", type: ClxNumberComponent, selector: "clx-number", inputs: ["variant", "size", "color", "label", "placeholder", "hint", "prefixIcon", "min", "max", "step", "value", "disabled"] }, { kind: "component", type: ClxRadioGroupComponent, selector: "clx-radio-group", inputs: ["color", "variant", "size", "direction", "disabled"] }, { kind: "component", type: ClxRadioComponent, selector: "clx-radio", inputs: ["value", "label", "color", "variant", "size"] }, { kind: "component", type: ClxSelectComponent, selector: "clx-select", inputs: ["label", "placeholder", "options", "multiple", "searchable", "searchPlaceholder", "activeColor", "size", "hint", "errorMessage", "disabled", "async", "loadOptions", "debounceMs", "minChars"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
14697
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: ClxIconComponent, selector: "span[clx-icon]", inputs: ["name", "size", "color", "fill"] }, { 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: ClxBadgeComponent, selector: "span[clx-badge]", inputs: ["variant", "color", "size", "shape", "positioned", "textColor"] }, { kind: "component", type: ClxCheckboxComponent, selector: "clx-checkbox", inputs: ["color", "variant", "size", "label", "value", "disabled"] }, { kind: "component", type: ClxNumberComponent, selector: "clx-number", inputs: ["variant", "size", "color", "label", "placeholder", "hint", "prefixIcon", "min", "max", "step", "value", "disabled"] }, { kind: "component", type: ClxRadioGroupComponent, selector: "clx-radio-group", inputs: ["color", "variant", "size", "direction", "disabled"] }, { kind: "component", type: ClxRadioComponent, selector: "clx-radio", inputs: ["value", "label", "color", "variant", "size"] }, { kind: "component", type: ClxSelectComponent, selector: "clx-select", inputs: ["label", "placeholder", "options", "multiple", "searchable", "searchPlaceholder", "activeColor", "size", "hint", "errorMessage", "disabled", "selectAll", "async", "loadOptions", "debounceMs", "minChars"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
14659
14698
  }
14660
14699
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ClxFilterPanelComponent, decorators: [{
14661
14700
  type: Component,
@@ -17419,6 +17458,10 @@ class ClxProductTagService {
17419
17458
  badgesFor(ctx) {
17420
17459
  const styles = this._styles() ?? [];
17421
17460
  const manual = new Set(ctx.tags);
17461
+ const units = ctx.stockQuantity;
17462
+ const low = styles.find(s => s.kind === 'LOW_STOCK');
17463
+ // "Últimas unidades" replaces "N en stock" — both would say the same.
17464
+ const isLow = !!low && ctx.inStock && units != null && units > 0 && units <= (low.stockThreshold ?? 0);
17422
17465
  const out = [];
17423
17466
  for (const s of styles) {
17424
17467
  let n = null;
@@ -17428,9 +17471,14 @@ class ClxProductTagService {
17428
17471
  continue;
17429
17472
  break;
17430
17473
  case 'STOCK':
17431
- if (!ctx.inStock || ctx.stockQuantity == null)
17474
+ if (!ctx.showStockCount || !ctx.inStock || units == null || isLow)
17432
17475
  continue;
17433
- n = ctx.stockQuantity;
17476
+ n = units;
17477
+ break;
17478
+ case 'LOW_STOCK':
17479
+ if (!isLow)
17480
+ continue;
17481
+ n = units;
17434
17482
  break;
17435
17483
  case 'OUT_OF_STOCK':
17436
17484
  if (ctx.inStock)
@@ -17567,7 +17615,9 @@ class ClxProductComponent {
17567
17615
  _badges = computed(() => {
17568
17616
  const p = this.product();
17569
17617
  return p
17570
- ? this._tagSvc.badgesFor({ tags: p.tags ?? [], inStock: p.in_stock, discountPercent: this._discountPct() })
17618
+ ? this._tagSvc.badgesFor({
17619
+ tags: p.tags ?? [], inStock: p.in_stock, stockQuantity: p.stock_quantity, discountPercent: this._discountPct(),
17620
+ })
17571
17621
  : [];
17572
17622
  }, ...(ngDevMode ? [{ debugName: "_badges" }] : /* istanbul ignore next */ []));
17573
17623
  _disabled = computed(() => !this.product()?.in_stock, ...(ngDevMode ? [{ debugName: "_disabled" }] : /* istanbul ignore next */ []));
@@ -18177,6 +18227,7 @@ class ClxProductDetailComponent {
18177
18227
  tags: p.tags ?? [],
18178
18228
  inStock: v.in_stock,
18179
18229
  stockQuantity: v.stock_quantity,
18230
+ showStockCount: true,
18180
18231
  discountPercent: p.has_discount ? this.discount() : 0,
18181
18232
  });
18182
18233
  }, ...(ngDevMode ? [{ debugName: "_badges" }] : /* istanbul ignore next */ []));
@@ -18667,7 +18718,7 @@ class ClxWishlistComponent {
18667
18718
  }
18668
18719
 
18669
18720
  </div>
18670
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { 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: ClxBadgeComponent, selector: "span[clx-badge]", inputs: ["variant", "color", "size", "shape", "positioned", "textColor"] }, { kind: "component", type: ClxSkeletonComponent, selector: "clx-skeleton", inputs: ["variant", "size", "width", "height"] }, { kind: "component", type: ClxPaginationComponent, selector: "clx-pagination", inputs: ["totalItems", "color", "borderColor", "shadow", "bordered", "size", "showPageSize", "pageSizeOptions", "currentPage", "pageSize"], outputs: ["currentPageChange", "pageSizeChange", "pageChange", "sizeChange"] }, { kind: "component", type: ClxInputComponent, selector: "clx-input", inputs: ["label", "placeholder", "type", "color", "hint", "prefixIcon", "suffixIcon", "prefixText", "suffixText", "status", "statusMessage", "size", "value", "disabled", "autocomplete"] }, { kind: "component", type: ClxSelectComponent, selector: "clx-select", inputs: ["label", "placeholder", "options", "multiple", "searchable", "searchPlaceholder", "activeColor", "size", "hint", "errorMessage", "disabled", "async", "loadOptions", "debounceMs", "minChars"] }, { kind: "directive", type: ClxTooltipDirective, selector: "[clxTooltip]", inputs: ["clxTooltip", "clxTooltipPosition", "clxTooltipColor", "clxTooltipSize", "clxTooltipDelay"] }, { kind: "component", type: ClxPageEmptyComponent, selector: "clx-page-empty", inputs: ["icon", "heading", "description", "ctaLabel", "ctaIcon", "ctaColor"], outputs: ["cta"] }, { kind: "pipe", type: CurrencyPipe, name: "currency" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
18721
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { 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: ClxBadgeComponent, selector: "span[clx-badge]", inputs: ["variant", "color", "size", "shape", "positioned", "textColor"] }, { kind: "component", type: ClxSkeletonComponent, selector: "clx-skeleton", inputs: ["variant", "size", "width", "height"] }, { kind: "component", type: ClxPaginationComponent, selector: "clx-pagination", inputs: ["totalItems", "color", "borderColor", "shadow", "bordered", "size", "showPageSize", "pageSizeOptions", "currentPage", "pageSize"], outputs: ["currentPageChange", "pageSizeChange", "pageChange", "sizeChange"] }, { kind: "component", type: ClxInputComponent, selector: "clx-input", inputs: ["label", "placeholder", "type", "color", "hint", "prefixIcon", "suffixIcon", "prefixText", "suffixText", "status", "statusMessage", "size", "value", "disabled", "autocomplete"] }, { kind: "component", type: ClxSelectComponent, selector: "clx-select", inputs: ["label", "placeholder", "options", "multiple", "searchable", "searchPlaceholder", "activeColor", "size", "hint", "errorMessage", "disabled", "selectAll", "async", "loadOptions", "debounceMs", "minChars"] }, { kind: "directive", type: ClxTooltipDirective, selector: "[clxTooltip]", inputs: ["clxTooltip", "clxTooltipPosition", "clxTooltipColor", "clxTooltipSize", "clxTooltipDelay"] }, { kind: "component", type: ClxPageEmptyComponent, selector: "clx-page-empty", inputs: ["icon", "heading", "description", "ctaLabel", "ctaIcon", "ctaColor"], outputs: ["cta"] }, { kind: "pipe", type: CurrencyPipe, name: "currency" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
18671
18722
  }
18672
18723
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ClxWishlistComponent, decorators: [{
18673
18724
  type: Component,
@@ -19681,9 +19732,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
19681
19732
  let _clxSearchIdCounter = 0;
19682
19733
  /** Storefront search bar — same color/size/CVA conventions as ClxInputComponent, but styled as
19683
19734
  * one continuous pill (input fused with a solid search button) rather than a bordered field with
19684
- * 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). */
19685
19743
  class ClxSearchComponent {
19686
19744
  _themeSvc = inject(ClxThemeService);
19745
+ _el = inject((ElementRef));
19746
+ _sso = inject(ScrollStrategyOptions);
19687
19747
  color = input(undefined, ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
19688
19748
  _color = computed(() => this.color() ?? this._themeSvc.config().primaryColor, ...(ngDevMode ? [{ debugName: "_color" }] : /* istanbul ignore next */ []));
19689
19749
  /** Overrides only the search button's text color — see ClxButtonComponent's textColor input. */
@@ -19696,13 +19756,45 @@ class ClxSearchComponent {
19696
19756
  value = input('', ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
19697
19757
  /** Disabled state for standalone use (without FormControl) */
19698
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 */ []));
19699
19769
  search = output();
19770
+ /** Debounced text for the host to fetch suggestions for ('' when cleared). */
19771
+ queryChange = output();
19772
+ suggestionSelect = output();
19700
19773
  _inputId = `clx-search-${++_clxSearchIdCounter}`;
19701
19774
  _value = signal('', ...(ngDevMode ? [{ debugName: "_value" }] : /* istanbul ignore next */ []));
19702
19775
  _cvaDisabled = signal(false, ...(ngDevMode ? [{ debugName: "_cvaDisabled" }] : /* istanbul ignore next */ []));
19703
19776
  _cvaConnected = false;
19704
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;
19705
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 */ []));
19706
19798
  _valueEffect = effect(() => {
19707
19799
  const v = this.value();
19708
19800
  if (!this._cvaConnected)
@@ -19710,6 +19802,9 @@ class ClxSearchComponent {
19710
19802
  }, ...(ngDevMode ? [{ debugName: "_valueEffect" }] : /* istanbul ignore next */ []));
19711
19803
  _onChange = () => { };
19712
19804
  _onTouched = () => { };
19805
+ constructor() {
19806
+ inject(DestroyRef).onDestroy(() => clearTimeout(this._debounce));
19807
+ }
19713
19808
  _sizeConfig = computed(() => INPUT_SIZE_MAP[this._size()], ...(ngDevMode ? [{ debugName: "_sizeConfig" }] : /* istanbul ignore next */ []));
19714
19809
  _buttonSize = computed(() => {
19715
19810
  switch (this._size()) {
@@ -19718,6 +19813,7 @@ class ClxSearchComponent {
19718
19813
  default: return 'md';
19719
19814
  }
19720
19815
  }, ...(ngDevMode ? [{ debugName: "_buttonSize" }] : /* istanbul ignore next */ []));
19816
+ _accentText = computed(() => resolveColor(this._color()).textEmphasis, ...(ngDevMode ? [{ debugName: "_accentText" }] : /* istanbul ignore next */ []));
19721
19817
  _wrapperClass = computed(() => {
19722
19818
  const focusCls = this._disabled() ? '' : resolveColor(this._color()).focusWithin;
19723
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`;
@@ -19746,34 +19842,106 @@ class ClxSearchComponent {
19746
19842
  const val = event.target.value;
19747
19843
  this._value.set(val);
19748
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('');
19749
19855
  }
19750
19856
  _handleBlur() {
19751
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);
19752
19907
  }
19753
19908
  _handleSubmit() {
19754
19909
  if (this._disabled())
19755
19910
  return;
19911
+ this._close();
19912
+ clearTimeout(this._debounce);
19756
19913
  this.search.emit(this._value());
19757
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
+ }
19758
19922
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ClxSearchComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
19759
- 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: [
19760
19924
  {
19761
19925
  provide: NG_VALUE_ACCESSOR,
19762
19926
  useExisting: forwardRef(() => ClxSearchComponent),
19763
19927
  multi: true,
19764
19928
  },
19765
19929
  ], ngImport: i0, template: `
19766
- <div [class]="_wrapperClass()">
19930
+ <div [class]="_wrapperClass()" cdkOverlayOrigin #origin="cdkOverlayOrigin">
19767
19931
  <input
19768
19932
  [id]="_inputId"
19769
19933
  type="search"
19934
+ role="combobox"
19935
+ [attr.aria-expanded]="_panelOpen()"
19936
+ [attr.aria-controls]="_inputId + '-panel'"
19770
19937
  [placeholder]="placeholder()"
19771
19938
  [disabled]="_disabled()"
19772
19939
  [value]="_value()"
19773
19940
  [class]="_inputClass()"
19774
19941
  autocomplete="off"
19775
19942
  (input)="_handleInput($event)"
19776
- (keydown.enter)="_handleSubmit()"
19943
+ (focus)="_handleFocus()"
19944
+ (keydown)="_handleKeydown($event)"
19777
19945
  (blur)="_handleBlur()"
19778
19946
  />
19779
19947
  <button
@@ -19790,14 +19958,77 @@ class ClxSearchComponent {
19790
19958
  {{ buttonLabel() }}
19791
19959
  </button>
19792
19960
  </div>
19793
- `, 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 });
19794
20025
  }
19795
20026
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ClxSearchComponent, decorators: [{
19796
20027
  type: Component,
19797
20028
  args: [{
19798
20029
  selector: 'clx-search',
19799
20030
  standalone: true,
19800
- imports: [ClxButtonComponent],
20031
+ imports: [OverlayModule, ClxButtonComponent, ClxIconComponent, ClxSpinnerComponent],
19801
20032
  providers: [
19802
20033
  {
19803
20034
  provide: NG_VALUE_ACCESSOR,
@@ -19806,17 +20037,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
19806
20037
  },
19807
20038
  ],
19808
20039
  template: `
19809
- <div [class]="_wrapperClass()">
20040
+ <div [class]="_wrapperClass()" cdkOverlayOrigin #origin="cdkOverlayOrigin">
19810
20041
  <input
19811
20042
  [id]="_inputId"
19812
20043
  type="search"
20044
+ role="combobox"
20045
+ [attr.aria-expanded]="_panelOpen()"
20046
+ [attr.aria-controls]="_inputId + '-panel'"
19813
20047
  [placeholder]="placeholder()"
19814
20048
  [disabled]="_disabled()"
19815
20049
  [value]="_value()"
19816
20050
  [class]="_inputClass()"
19817
20051
  autocomplete="off"
19818
20052
  (input)="_handleInput($event)"
19819
- (keydown.enter)="_handleSubmit()"
20053
+ (focus)="_handleFocus()"
20054
+ (keydown)="_handleKeydown($event)"
19820
20055
  (blur)="_handleBlur()"
19821
20056
  />
19822
20057
  <button
@@ -19833,6 +20068,69 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
19833
20068
  {{ buttonLabel() }}
19834
20069
  </button>
19835
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>
19836
20134
  `,
19837
20135
  encapsulation: ViewEncapsulation.None,
19838
20136
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -19840,7 +20138,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
19840
20138
  class: 'block w-full',
19841
20139
  },
19842
20140
  }]
19843
- }], 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"] }] } });
19844
20142
 
19845
20143
  class ClxAnimateGroupDirective {
19846
20144
  stagger = input(100, { ...(ngDevMode ? { debugName: "stagger" } : /* istanbul ignore next */ {}), transform: numberAttribute });