@spartan-ng/brain 0.0.1-alpha.613 → 0.0.1-alpha.628

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.
@@ -1,6 +1,6 @@
1
1
  import { ActiveDescendantKeyManager } from '@angular/cdk/a11y';
2
2
  import * as i0 from '@angular/core';
3
- import { Directive, InjectionToken, inject, forwardRef, Injector, input, booleanAttribute, linkedSignal, computed, model, contentChild, ElementRef, contentChildren, effect, Renderer2, TemplateRef, ViewContainerRef, PLATFORM_ID, signal } from '@angular/core';
3
+ import { Directive, InjectionToken, inject, forwardRef, Injector, input, booleanAttribute, linkedSignal, computed, model, contentChild, ElementRef, contentChildren, signal, afterNextRender, effect, untracked, Renderer2, TemplateRef, ViewContainerRef, PLATFORM_ID } from '@angular/core';
4
4
  import { NG_VALUE_ACCESSOR } from '@angular/forms';
5
5
  import { BrnPopover } from '@spartan-ng/brain/popover';
6
6
  import { stringifyAsLabel } from '@spartan-ng/brain/core';
@@ -17,8 +17,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.7", ngImpor
17
17
  }] });
18
18
 
19
19
  const BrnComboboxItemToken = new InjectionToken('BrnComboboxItemToken');
20
- function provideBrnComboboxItem(Combobox) {
21
- return { provide: BrnComboboxItemToken, useExisting: Combobox };
20
+ function provideBrnComboboxItem(comboboxItem) {
21
+ return { provide: BrnComboboxItemToken, useExisting: comboboxItem };
22
22
  }
23
23
 
24
24
  const comboboxContainsFilter = (item, query, collator, itemToString) => {
@@ -69,6 +69,7 @@ function getDefaultConfig() {
69
69
  filter: (itemValue, search, collator, itemToString) => comboboxContainsFilter(itemValue, search, collator, itemToString),
70
70
  isItemEqualToValue: (itemValue, selectedValue) => Object.is(itemValue, selectedValue),
71
71
  itemToString: undefined,
72
+ autoHighlight: false,
72
73
  };
73
74
  }
74
75
  const BrnComboboxConfigToken = new InjectionToken('BrnComboboxConfig');
@@ -109,6 +110,10 @@ class BrnCombobox {
109
110
  itemToString = input(this._config.itemToString);
110
111
  /** A custom filter function to use when searching. */
111
112
  filter = input(this._config.filter);
113
+ /** Whether to auto-highlight the first matching item. */
114
+ autoHighlight = input(this._config.autoHighlight, {
115
+ transform: booleanAttribute,
116
+ });
112
117
  /** The selected value of the combobox. */
113
118
  value = model(null);
114
119
  /** The current search query. */
@@ -131,6 +136,8 @@ class BrnCombobox {
131
136
  keyManager = new ActiveDescendantKeyManager(this.items, this._injector);
132
137
  /** @internal Whether the combobox is expanded */
133
138
  isExpanded = computed(() => this._brnPopover?.stateComputed() === 'open');
139
+ _comboboxInput = signal(undefined);
140
+ mode = computed(() => this._comboboxInput()?.mode() || 'combobox');
134
141
  _onChange;
135
142
  _onTouched;
136
143
  constructor() {
@@ -143,6 +150,24 @@ class BrnCombobox {
143
150
  this.search.set('');
144
151
  this.keyManager.setActiveItem(-1);
145
152
  });
153
+ afterNextRender(() => {
154
+ effect(() => {
155
+ if (!this.autoHighlight() || !this.isExpanded() || !this.search())
156
+ return;
157
+ const hasVisibleItems = this.visibleItems();
158
+ untracked(() => {
159
+ if (hasVisibleItems) {
160
+ this.keyManager.setFirstItemActive();
161
+ }
162
+ else {
163
+ this.keyManager.setActiveItem(-1);
164
+ }
165
+ });
166
+ }, { injector: this._injector });
167
+ });
168
+ }
169
+ registerComboboxInput(input) {
170
+ this._comboboxInput.set(input);
146
171
  }
147
172
  isSelected(itemValue) {
148
173
  return this.isItemEqualToValue()(itemValue, this.value());
@@ -157,14 +182,20 @@ class BrnCombobox {
157
182
  if (!this.isExpanded())
158
183
  return;
159
184
  const value = this.keyManager.activeItem?.value();
160
- if (value === undefined)
161
- return;
162
- this.select(value);
185
+ if (value) {
186
+ this.select(value);
187
+ }
188
+ else {
189
+ this.close();
190
+ }
163
191
  }
164
192
  resetValue() {
165
193
  this.value.set(null);
166
194
  this._onChange?.(null);
167
195
  }
196
+ resetSearch() {
197
+ this.search.set('');
198
+ }
168
199
  removeValue(_) {
169
200
  console.warn('BrnComboboxChipRemove only works with multiple selection comboboxes.');
170
201
  }
@@ -195,7 +226,7 @@ class BrnCombobox {
195
226
  this._disabled.set(isDisabled);
196
227
  }
197
228
  /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.7", ngImport: i0, type: BrnCombobox, deps: [], target: i0.ɵɵFactoryTarget.Directive });
198
- /** @nocollapse */ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "20.0.7", type: BrnCombobox, isStandalone: true, selector: "[brnCombobox]", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, filterOptions: { classPropertyName: "filterOptions", publicName: "filterOptions", isSignal: true, isRequired: false, transformFunction: null }, isItemEqualToValue: { classPropertyName: "isItemEqualToValue", publicName: "isItemEqualToValue", isSignal: true, isRequired: false, transformFunction: null }, itemToString: { classPropertyName: "itemToString", publicName: "itemToString", isSignal: true, isRequired: false, transformFunction: null }, filter: { classPropertyName: "filter", publicName: "filter", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, search: { classPropertyName: "search", publicName: "search", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", search: "searchChange" }, providers: [provideBrnComboboxBase(BrnCombobox), BRN_COMBOBOX_VALUE_ACCESSOR], queries: [{ propertyName: "_searchInputWrapper", first: true, predicate: BrnComboboxInputWrapper, descendants: true, read: ElementRef, isSignal: true }, { propertyName: "items", predicate: BrnComboboxItemToken, descendants: true, isSignal: true }], ngImport: i0 });
229
+ /** @nocollapse */ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "20.0.7", type: BrnCombobox, isStandalone: true, selector: "[brnCombobox]", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, filterOptions: { classPropertyName: "filterOptions", publicName: "filterOptions", isSignal: true, isRequired: false, transformFunction: null }, isItemEqualToValue: { classPropertyName: "isItemEqualToValue", publicName: "isItemEqualToValue", isSignal: true, isRequired: false, transformFunction: null }, itemToString: { classPropertyName: "itemToString", publicName: "itemToString", isSignal: true, isRequired: false, transformFunction: null }, filter: { classPropertyName: "filter", publicName: "filter", isSignal: true, isRequired: false, transformFunction: null }, autoHighlight: { classPropertyName: "autoHighlight", publicName: "autoHighlight", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, search: { classPropertyName: "search", publicName: "search", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", search: "searchChange" }, providers: [provideBrnComboboxBase(BrnCombobox), BRN_COMBOBOX_VALUE_ACCESSOR], queries: [{ propertyName: "_searchInputWrapper", first: true, predicate: BrnComboboxInputWrapper, descendants: true, read: ElementRef, isSignal: true }, { propertyName: "items", predicate: BrnComboboxItemToken, descendants: true, isSignal: true }], ngImport: i0 });
199
230
  }
200
231
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.7", ngImport: i0, type: BrnCombobox, decorators: [{
201
232
  type: Directive,
@@ -369,7 +400,15 @@ class BrnComboboxClear {
369
400
  _templateRef = inject(TemplateRef);
370
401
  _viewContainerRef = inject(ViewContainerRef);
371
402
  /** Determine if the combobox has a value */
372
- _hasValue = computed(() => this._combobox.value() !== null);
403
+ _hasValue = computed(() => {
404
+ const mode = this._combobox.mode();
405
+ if (mode === 'combobox') {
406
+ return this._combobox.value() !== null;
407
+ }
408
+ else {
409
+ return this._combobox.search() !== '';
410
+ }
411
+ });
373
412
  constructor() {
374
413
  effect(() => {
375
414
  this._viewContainerRef.clear();
@@ -385,7 +424,12 @@ class BrnComboboxClear {
385
424
  });
386
425
  }
387
426
  clear() {
388
- this._combobox.resetValue();
427
+ if (this._combobox.mode() === 'combobox') {
428
+ this._combobox.resetValue();
429
+ }
430
+ else {
431
+ this._combobox.resetSearch();
432
+ }
389
433
  }
390
434
  /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.7", ngImport: i0, type: BrnComboboxClear, deps: [], target: i0.ɵɵFactoryTarget.Directive });
391
435
  /** @nocollapse */ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.0.7", type: BrnComboboxClear, isStandalone: true, selector: "[brnComboboxClear]", ngImport: i0 });
@@ -463,31 +507,26 @@ class BrnComboboxInput {
463
507
  _el = inject(ElementRef);
464
508
  _combobox = injectBrnComboboxBase();
465
509
  _content = inject(BrnComboboxContent, { optional: true });
466
- _mode = computed(() => (this._content ? 'popup' : 'combobox'));
510
+ mode = computed(() => (this._content ? 'popup' : 'combobox'));
467
511
  /** The id of the combobox input */
468
512
  id = input(`brn-combobox-input-${++BrnComboboxInput._id}`);
469
513
  disabled = this._combobox.disabledState;
470
514
  /** Whether the combobox panel is expanded */
471
515
  _isExpanded = this._combobox.isExpanded;
472
516
  constructor() {
517
+ this._combobox.registerComboboxInput?.(this);
473
518
  effect(() => {
474
- const mode = this._mode();
519
+ const mode = this.mode();
475
520
  const value = this._combobox.value();
476
521
  const search = this._combobox.search();
477
- switch (mode) {
478
- case 'combobox':
479
- if (value && search === '') {
480
- this._el.nativeElement.value = stringifyAsLabel(value, this._combobox.itemToString());
481
- }
482
- else if (search === '') {
483
- this._el.nativeElement.value = '';
484
- }
485
- break;
486
- case 'popup':
487
- if (search === '') {
488
- this._el.nativeElement.value = '';
489
- }
490
- break;
522
+ // In combobox mode we want to display the label of the selected value if no search is active
523
+ if (mode === 'combobox' && value && search === '') {
524
+ this._el.nativeElement.value = stringifyAsLabel(value, this._combobox.itemToString());
525
+ return;
526
+ }
527
+ // Otherwise we want to update the input value to the search value
528
+ if (this._el.nativeElement.value !== search) {
529
+ this._el.nativeElement.value = search;
491
530
  }
492
531
  });
493
532
  }
@@ -495,7 +534,7 @@ class BrnComboboxInput {
495
534
  const value = event.target.value;
496
535
  this._combobox.search.set(value);
497
536
  this._combobox.open();
498
- if (value === '' && this._mode() === 'combobox') {
537
+ if (value === '' && this.mode() === 'combobox') {
499
538
  this._combobox.resetValue();
500
539
  }
501
540
  }
@@ -669,6 +708,10 @@ class BrnComboboxMultiple {
669
708
  itemToString = input(this._config.itemToString);
670
709
  /** A custom filter function to use when searching. */
671
710
  filter = input(this._config.filter);
711
+ /** Whether to auto-highlight the first matching item. */
712
+ autoHighlight = input(this._config.autoHighlight, {
713
+ transform: booleanAttribute,
714
+ });
672
715
  /** The selected values of the combobox. */
673
716
  value = model(null);
674
717
  /** The current search query. */
@@ -690,6 +733,7 @@ class BrnComboboxMultiple {
690
733
  keyManager = new ActiveDescendantKeyManager(this.items, this._injector);
691
734
  /** @internal Whether the autocomplete is expanded */
692
735
  isExpanded = computed(() => this._brnPopover?.stateComputed() === 'open');
736
+ mode = signal('combobox').asReadonly();
693
737
  _onChange;
694
738
  _onTouched;
695
739
  constructor() {
@@ -702,6 +746,21 @@ class BrnComboboxMultiple {
702
746
  this.search.set('');
703
747
  this.keyManager.setActiveItem(-1);
704
748
  });
749
+ afterNextRender(() => {
750
+ effect(() => {
751
+ if (!this.autoHighlight() || !this.isExpanded() || !this.search())
752
+ return;
753
+ const hasVisibleItems = this.visibleItems();
754
+ untracked(() => {
755
+ if (hasVisibleItems) {
756
+ this.keyManager.setFirstItemActive();
757
+ }
758
+ else {
759
+ this.keyManager.setActiveItem(-1);
760
+ }
761
+ });
762
+ }, { injector: this._injector });
763
+ });
705
764
  }
706
765
  isSelected(itemValue) {
707
766
  return this.value()?.some((v) => this.isItemEqualToValue()(itemValue, v)) ?? false;
@@ -726,14 +785,20 @@ class BrnComboboxMultiple {
726
785
  if (!this.isExpanded())
727
786
  return;
728
787
  const value = this.keyManager.activeItem?.value();
729
- if (value === undefined)
730
- return;
731
- this.select(value);
788
+ if (value) {
789
+ this.select(value);
790
+ }
791
+ else {
792
+ this.close();
793
+ }
732
794
  }
733
795
  resetValue() {
734
796
  this.value.set(null);
735
797
  this._onChange?.(null);
736
798
  }
799
+ resetSearch() {
800
+ this.search.set('');
801
+ }
737
802
  removeValue(itemValue) {
738
803
  const selected = this.value() ?? [];
739
804
  this.value.set(selected.filter((value) => !this.isItemEqualToValue()(itemValue, value)) ?? []);
@@ -771,7 +836,7 @@ class BrnComboboxMultiple {
771
836
  this._disabled.set(isDisabled);
772
837
  }
773
838
  /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.7", ngImport: i0, type: BrnComboboxMultiple, deps: [], target: i0.ɵɵFactoryTarget.Directive });
774
- /** @nocollapse */ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "20.0.7", type: BrnComboboxMultiple, isStandalone: true, selector: "[brnCombobox]", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, filterOptions: { classPropertyName: "filterOptions", publicName: "filterOptions", isSignal: true, isRequired: false, transformFunction: null }, isItemEqualToValue: { classPropertyName: "isItemEqualToValue", publicName: "isItemEqualToValue", isSignal: true, isRequired: false, transformFunction: null }, itemToString: { classPropertyName: "itemToString", publicName: "itemToString", isSignal: true, isRequired: false, transformFunction: null }, filter: { classPropertyName: "filter", publicName: "filter", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, search: { classPropertyName: "search", publicName: "search", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", search: "searchChange" }, providers: [provideBrnComboboxBase(BrnComboboxMultiple), BRN_COMBOBOX_MULTIPLE_VALUE_ACCESSOR], queries: [{ propertyName: "_searchInputWrapper", first: true, predicate: BrnComboboxInputWrapper, descendants: true, read: ElementRef, isSignal: true }, { propertyName: "items", predicate: BrnComboboxItemToken, descendants: true, isSignal: true }], ngImport: i0 });
839
+ /** @nocollapse */ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "20.0.7", type: BrnComboboxMultiple, isStandalone: true, selector: "[brnCombobox]", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, filterOptions: { classPropertyName: "filterOptions", publicName: "filterOptions", isSignal: true, isRequired: false, transformFunction: null }, isItemEqualToValue: { classPropertyName: "isItemEqualToValue", publicName: "isItemEqualToValue", isSignal: true, isRequired: false, transformFunction: null }, itemToString: { classPropertyName: "itemToString", publicName: "itemToString", isSignal: true, isRequired: false, transformFunction: null }, filter: { classPropertyName: "filter", publicName: "filter", isSignal: true, isRequired: false, transformFunction: null }, autoHighlight: { classPropertyName: "autoHighlight", publicName: "autoHighlight", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, search: { classPropertyName: "search", publicName: "search", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", search: "searchChange" }, providers: [provideBrnComboboxBase(BrnComboboxMultiple), BRN_COMBOBOX_MULTIPLE_VALUE_ACCESSOR], queries: [{ propertyName: "_searchInputWrapper", first: true, predicate: BrnComboboxInputWrapper, descendants: true, read: ElementRef, isSignal: true }, { propertyName: "items", predicate: BrnComboboxItemToken, descendants: true, isSignal: true }], ngImport: i0 });
775
840
  }
776
841
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.7", ngImport: i0, type: BrnComboboxMultiple, decorators: [{
777
842
  type: Directive,