diff --git a/src/app/components/autocomplete/autocomplete.css b/src/app/components/autocomplete/autocomplete.css deleted file mode 100755 index aa8619eff26..00000000000 --- a/src/app/components/autocomplete/autocomplete.css +++ /dev/null @@ -1,104 +0,0 @@ -@layer primeng { - .p-autocomplete { - display: inline-flex; - position: relative; - } - - .p-autocomplete-loader { - position: absolute; - top: 50%; - margin-top: -0.5rem; - } - - .p-autocomplete-dd .p-autocomplete-input { - flex: 1 1 auto; - width: 1%; - } - - .p-autocomplete-dd .p-autocomplete-input, - .p-autocomplete-dd .p-autocomplete-multiple-container { - border-top-right-radius: 0; - border-bottom-right-radius: 0; - } - - .p-autocomplete-dd .p-autocomplete-dropdown { - border-top-left-radius: 0; - border-bottom-left-radius: 0px; - } - - .p-autocomplete-panel { - overflow: auto; - } - - .p-autocomplete-items { - margin: 0; - padding: 0; - list-style-type: none; - } - - .p-autocomplete-item { - cursor: pointer; - white-space: nowrap; - position: relative; - overflow: hidden; - } - - .p-autocomplete-multiple-container { - margin: 0; - padding: 0; - list-style-type: none; - cursor: text; - overflow: hidden; - display: flex; - align-items: center; - flex-wrap: wrap; - } - - .p-autocomplete-token { - width: fit-content; - cursor: default; - display: inline-flex; - align-items: center; - flex: 0 0 auto; - } - - .p-autocomplete-token-icon { - display: flex; - cursor: pointer; - } - - .p-autocomplete-input-token { - flex: 1 1 auto; - display: inline-flex; - } - - .p-autocomplete-input-token input { - border: 0 none; - outline: 0 none; - background-color: transparent; - margin: 0; - padding: 0; - box-shadow: none; - border-radius: 0; - width: 100%; - } - - .p-fluid .p-autocomplete { - display: flex; - } - - .p-fluid .p-autocomplete-dd .p-autocomplete-input { - width: 1%; - } - - .p-autocomplete-clear-icon { - position: absolute; - top: 50%; - margin-top: -0.5rem; - cursor: pointer; - } - - .p-autocomplete-clearable { - position: relative; - } -} diff --git a/src/app/components/autocomplete/autocomplete.ts b/src/app/components/autocomplete/autocomplete.ts index 90ef3c12a23..eeecb7c3ba3 100755 --- a/src/app/components/autocomplete/autocomplete.ts +++ b/src/app/components/autocomplete/autocomplete.ts @@ -13,6 +13,7 @@ import { ElementRef, EventEmitter, forwardRef, + inject, Inject, Input, NgModule, @@ -45,6 +46,8 @@ import { ChevronDownIcon } from 'primeng/icons/chevrondown'; import { Nullable, VoidListener } from 'primeng/ts-helpers'; import { AutoCompleteCompleteEvent, AutoCompleteDropdownClickEvent, AutoCompleteLazyLoadEvent, AutoCompleteSelectEvent, AutoCompleteUnselectEvent } from './autocomplete.interface'; import { ChipModule, ChipProps } from 'primeng/chip'; +import { AutoCompleteStyle } from './style/autocompletestyle'; +import { BaseComponent } from 'primeng/basecomponent'; export const AUTOCOMPLETE_VALUE_ACCESSOR: any = { provide: NG_VALUE_ACCESSOR, @@ -58,14 +61,14 @@ export const AUTOCOMPLETE_VALUE_ACCESSOR: any = { @Component({ selector: 'p-autoComplete', template: ` -
+
- + - - + + - + -
  • +
  • - + -
      +
        -
      • +
      • {{ getOptionGroupLabel(option.optionGroup) }}
  • `, - host: { - class: 'p-element p-inputwrapper', - '[class.p-inputwrapper-filled]': 'filled', - '[class.p-inputwrapper-focus]': '((focused && !disabled) || autofocus) || overlayVisible', - '[class.p-autocomplete-clearable]': 'showClear && !disabled' - }, - providers: [AUTOCOMPLETE_VALUE_ACCESSOR], + providers: [AUTOCOMPLETE_VALUE_ACCESSOR, AutoCompleteStyle], changeDetection: ChangeDetectionStrategy.OnPush, - encapsulation: ViewEncapsulation.None, - styleUrls: ['./autocomplete.css'] + encapsulation: ViewEncapsulation.None }) -export class AutoComplete implements AfterViewChecked, AfterContentInit, OnDestroy, ControlValueAccessor { +export class AutoComplete extends BaseComponent implements AfterViewChecked, AfterContentInit, OnDestroy, ControlValueAccessor { /** * Minimum number of characters to initiate a search. * @group Props @@ -629,6 +614,12 @@ export class AutoComplete implements AfterViewChecked, AfterContentInit, OnDestr * @group Props */ @Input({ transform: booleanAttribute }) focusOnHover: boolean | undefined; + /** + * Whether typeahead is active or not. + * @defaultValue true + * @group Props + */ + @Input({ transform: booleanAttribute }) typeahead: boolean = true; /** * Specifies the input variant of the component. * @group Props @@ -818,26 +809,37 @@ export class AutoComplete implements AfterViewChecked, AfterContentInit, OnDestr get focusedOptionId() { return this.focusedOptionIndex() !== -1 ? `${this.id}_${this.focusedOptionIndex()}` : null; } + // host: { + // class: 'p-element p-inputwrapper', + // '[class.p-inputwrapper-filled]': 'filled', + // '[class.p-inputwrapper-focus]': '((focused && !disabled) || autofocus) || overlayVisible', + // '[class.p-autocomplete-clearable]': 'showClear && !disabled' + // }, + _componentStyle = inject(AutoCompleteStyle); - get containerClass() { - return { - 'p-autocomplete p-component p-inputwrapper': true, - 'p-disabled': this.disabled, - 'p-focus': this.focused, - 'p-autocomplete-dd': this.dropdown, - 'p-autocomplete-multiple': this.multiple, - 'p-inputwrapper-focus': this.focused, - 'p-overlay-open': this.overlayVisible - }; + get rootClass() { + return this._componentStyle.classes.root({ instance: this }); } - get multiContainerClass() { - return { 'p-autocomplete-multiple-container p-component p-inputtext': true, 'p-variant-filled': this.variant === 'filled' || this.config.inputStyle() === 'filled' }; + get inputMultipleClass() { + return this._componentStyle.classes.inputMultiple({ instance: this }); } + chipItemClass(index) { + return this._componentStyle.classes.chipItem({ instance: this, i: index }); + } + + optionClass(option, i, scrollerOptions) { + return { 'p-autocomplete-option': true, 'p-autocomplete-option-selected': this.isSelected(option), 'p-focus': this.focusedOptionIndex() === this.getOptionIndex(i, scrollerOptions), 'p-disabled': this.isOptionDisabled(option) }; + } + + // get multiContainerClass() { + // return { 'p-autocomplete-multiple-container p-component p-inputtext': true, 'p-variant-filled': this.variant === 'filled' || this.config.inputStyle() === 'filled' }; + // } + get panelClass() { return { - 'p-autocomplete-panel p-component': true, + 'p-autocomplete-overlay p-component': true, 'p-input-filled': this.config.inputStyle() === 'filled', 'p-ripple-disabled': this.config.ripple === false }; @@ -890,21 +892,15 @@ export class AutoComplete implements AfterViewChecked, AfterContentInit, OnDestr return typeof this.modelValue() === 'string' && this.optionValue; } - constructor( - @Inject(DOCUMENT) private document: Document, - public el: ElementRef, - public renderer: Renderer2, - public cd: ChangeDetectorRef, - public config: PrimeNGConfig, - public overlayService: OverlayService, - private zone: NgZone - ) { + constructor(public overlayService: OverlayService, private zone: NgZone) { + super(); effect(() => { this.filled = ObjectUtils.isNotEmpty(this.modelValue()); }); } ngOnInit() { + super.ngOnInit(); this.id = this.id || UniqueComponentId(); this.cd.detectChanges(); } @@ -1105,34 +1101,36 @@ export class AutoComplete implements AfterViewChecked, AfterContentInit, OnDestr } onInput(event) { - if (this.searchTimeout) { - clearTimeout(this.searchTimeout); - } - - let query = event.target.value; - if (this.maxlength !== null) { - query = query.split('').slice(0, this.maxlength).join(''); - } + if (this.typeahead) { + if (this.searchTimeout) { + clearTimeout(this.searchTimeout); + } - if (!this.multiple && !this.forceSelection) { - this.updateModel(query); - } + let query = event.target.value; + if (this.maxlength !== null) { + query = query.split('').slice(0, this.maxlength).join(''); + } - if (query.length === 0 && !this.multiple) { - this.onClear.emit(); + if (!this.multiple && !this.forceSelection) { + this.updateModel(query); + } - setTimeout(() => { - this.hide(); - }, this.delay / 2); - } else { - if (query.length >= this.minLength) { - this.focusedOptionIndex.set(-1); + if (query.length === 0 && !this.multiple) { + this.onClear.emit(); - this.searchTimeout = setTimeout(() => { - this.search(event, query, 'input'); - }, this.delay); + setTimeout(() => { + this.hide(); + }, this.delay / 2); } else { - this.hide(); + if (query.length >= this.minLength) { + this.focusedOptionIndex.set(-1); + + this.searchTimeout = setTimeout(() => { + this.search(event, query, 'input'); + }, this.delay); + } else { + this.hide(); + } } } } @@ -1381,6 +1379,12 @@ export class AutoComplete implements AfterViewChecked, AfterContentInit, OnDestr } onEnterKey(event) { + if (!this.typeahead) { + if (this.multiple) { + this.updateModel([...(this.modelValue() || []), event.target.value]); + this.inputEL.nativeElement.value = ''; + } + } if (!this.overlayVisible) { this.onArrowDownKey(event); } else { @@ -1668,6 +1672,8 @@ export class AutoComplete implements AfterViewChecked, AfterContentInit, OnDestr this.scrollHandler.destroy(); this.scrollHandler = null; } + + super.ngOnDestroy(); } } diff --git a/src/app/components/autocomplete/style/autocompletestyle.ts b/src/app/components/autocomplete/style/autocompletestyle.ts new file mode 100644 index 00000000000..1bfa93fd4d4 --- /dev/null +++ b/src/app/components/autocomplete/style/autocompletestyle.ts @@ -0,0 +1,309 @@ +import { Injectable } from '@angular/core'; +import { BaseStyle } from 'primeng/base'; +import { ObjectUtils } from 'primeng/utils'; + +const theme = ({ dt }) => ` +.p-autocomplete { + display: inline-flex; +} + +.p-autocomplete-loader { + position: absolute; + top: 50%; + margin-top: -0.5rem; + right: ${dt('autocomplete.padding.x')}; +} + +.p-autocomplete:has(.p-autocomplete-dropdown) .p-autocomplete-loader { + right: calc(${dt('autocomplete.dropdown.width')} + ${dt('autocomplete.padding.x')}); +} + +.p-autocomplete:has(.p-autocomplete-dropdown) .p-autocomplete-input { + flex: 1 1 auto; + width: 1%; +} + +.p-autocomplete:has(.p-autocomplete-dropdown) .p-autocomplete-input, +.p-autocomplete:has(.p-autocomplete-dropdown) .p-autocomplete-input-multiple { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.p-autocomplete-dropdown { + cursor: pointer; + display: inline-flex; + cursor: pointer; + user-select: none; + align-items: center; + justify-content: center; + overflow: hidden; + position: relative; + width: ${dt('autocomplete.dropdown.width')}; + border-top-right-radius: ${dt('autocomplete.dropdown.border.radius')}; + border-bottom-right-radius: ${dt('autocomplete.dropdown.border.radius')}; + background: ${dt('autocomplete.dropdown.background')}; + border: 1px solid ${dt('autocomplete.dropdown.border.color')}; + border-left: 0 none; + color: ${dt('autocomplete.dropdown.color')}; + transition: background ${dt('autocomplete.transition.duration')}, color ${dt('autocomplete.transition.duration')}, border-color ${dt('autocomplete.transition.duration')}, outline-color ${dt('autocomplete.transition.duration')}, box-shadow ${dt( + 'autocomplete.transition.duration' +)}; + outline-color: transparent; +} + +.p-autocomplete-dropdown:not(:disabled):hover { + background: ${dt('autocomplete.dropdown.hover.background')}; + border-color: ${dt('autocomplete.dropdown.hover.border.color')}; + color: ${dt('autocomplete.dropdown.hover.color')}; +} + +.p-autocomplete-dropdown:not(:disabled):active { + background: ${dt('autocomplete.dropdown.active.background')}; + border-color: ${dt('autocomplete.dropdown.active.border.color')}; + color: ${dt('autocomplete.dropdown.active.color')}; +} + +.p-autocomplete-dropdown:focus-visible { + box-shadow: ${dt('autocomplete.dropdown.focus.ring.shadow')}; + outline: ${dt('autocomplete.dropdown.focus.ring.width')} ${dt('autocomplete.dropdown.focus.ring.style')} ${dt('autocomplete.dropdown.focus.ring.color')}; + outline-offset: ${dt('autocomplete.dropdown.focus.ring.offset')}; +} + +.p-autocomplete .p-autocomplete-overlay { + min-width: 100%; +} + +.p-autocomplete-overlay { + position: absolute; + overflow: auto; + top: 0; + left: 0; + background: ${dt('autocomplete.overlay.background')}; + color: ${dt('autocomplete.overlay.color')}; + border: 1px solid ${dt('autocomplete.overlay.border.color')}; + border-radius: ${dt('autocomplete.overlay.border.radius')}; + box-shadow: ${dt('autocomplete.overlay.shadow')}; +} + +.p-autocomplete-list { + margin: 0; + padding: 0; + list-style-type: none; + display: flex; + flex-direction: column; + gap: ${dt('autocomplete.list.gap')}; + padding: ${dt('autocomplete.list.padding')}; +} + +.p-autocomplete-option { + cursor: pointer; + white-space: nowrap; + position: relative; + overflow: hidden; + display: flex; + align-items: center; + padding: ${dt('autocomplete.option.padding')}; + border: 0 none; + color: ${dt('autocomplete.option.color')}; + background: transparent; + transition: background ${dt('autocomplete.transition.duration')}, color ${dt('autocomplete.transition.duration')}, border-color ${dt('autocomplete.transition.duration')}; + border-radius: ${dt('autocomplete.option.border.radius')}; +} + +.p-autocomplete-option:not(.p-autocomplete-option-selected):not(.p-disabled).p-focus { + background: ${dt('autocomplete.option.focus.background')}; + color: ${dt('autocomplete.option.focus.color')}; +} + +.p-autocomplete-option-selected { + background: ${dt('autocomplete.option.selected.background')}; + color: ${dt('autocomplete.option.selected.color')}; +} + +.p-autocomplete-option-selected.p-focus { + background: ${dt('autocomplete.option.selected.focus.background')}; + color: ${dt('autocomplete.option.selected.focus.color')}; +} + +.p-autocomplete-option-group { + margin: 0; + padding: ${dt('autocomplete.option.group.padding')}; + color: ${dt('autocomplete.option.group.color')}; + background: ${dt('autocomplete.option.group.background')}; + font-weight: ${dt('autocomplete.option.group.font.weight')}; +} + +.p-autocomplete-input-multiple { + margin: 0; + list-style-type: none; + cursor: text; + overflow: hidden; + display: flex; + align-items: center; + flex-wrap: wrap; + padding: calc(${dt('autocomplete.padding.y')} / 2) ${dt('autocomplete.padding.x')}; + gap: calc(${dt('autocomplete.padding.y')} / 2); + color: ${dt('autocomplete.color')}; + background: ${dt('autocomplete.background')}; + border: 1px solid ${dt('autocomplete.border.color')}; + border-radius: ${dt('autocomplete.border.radius')}; + width: 100%; + transition: background ${dt('autocomplete.transition.duration')}, color ${dt('autocomplete.transition.duration')}, border-color ${dt('autocomplete.transition.duration')}, outline-color ${dt('autocomplete.transition.duration')}, box-shadow ${dt( + 'autocomplete.transition.duration' +)}; + outline-color: transparent; + box-shadow: ${dt('autocomplete.shadow')}; +} + +.p-autocomplete:not(.p-disabled):hover .p-autocomplete-input-multiple { + border-color: ${dt('autocomplete.hover.border.color')}; +} + +.p-autocomplete:not(.p-disabled).p-focus .p-autocomplete-input-multiple { + border-color: ${dt('autocomplete.focus.border.color')}; + box-shadow: ${dt('autocomplete.focus.ring.shadow')}; + outline: ${dt('autocomplete.focus.ring.width')} ${dt('autocomplete.focus.ring.style')} ${dt('autocomplete.focus.ring.color')}; + outline-offset: ${dt('autocomplete.focus.ring.offset')}; +} + +.p-variant-filled.p-autocomplete-input-multiple { + background: ${dt('autocomplete.filled.background')}; +} + +.p-autocomplete:not(.p-disabled).p-focus .p-variant-filled.p-autocomplete-input-multiple { + background: ${dt('autocomplete.filled.focus.background')}; +} + +.p-autocomplete.p-disabled .p-autocomplete-input-multiple { + opacity: 1; + background: ${dt('autocomplete.disabled.background')}; + color: ${dt('autocomplete.disabled.color')}; +} + +.p-autocomplete-chip.p-chip { + padding-top: calc(${dt('autocomplete.padding.y')} / 2); + padding-bottom: calc(${dt('autocomplete.padding.y')} / 2); + border-radius: ${dt('autocomplete.chip.border.radius')}; +} + +.p-autocomplete-input-multiple:has(.p-autocomplete-chip) { + padding-left: calc(${dt('autocomplete.padding.y')} / 2); + padding-right: calc(${dt('autocomplete.padding.y')} / 2); +} + +.p-autocomplete-chip-item.p-focus .p-autocomplete-chip { + background: ${dt('inputchips.chip.focus.background')}; + color: ${dt('inputchips.chip.focus.color')}; +} + +.p-autocomplete-input-chip { + flex: 1 1 auto; + display: inline-flex; + padding-top: calc(${dt('autocomplete.padding.y')} / 2); + padding-bottom: calc(${dt('autocomplete.padding.y')} / 2); +} + +.p-autocomplete-input-chip input { + border: 0 none; + outline: 0 none; + background: transparent; + margin: 0; + padding: 0; + box-shadow: none; + border-radius: 0; + width: 100%; + font-family: inherit; + font-feature-settings: inherit; + font-size: 1rem; + color: inherit; +} + +.p-autocomplete-input-chip input::placeholder { + color: ${dt('autocomplete.placeholder.color')}; +} + +.p-autocomplete-empty-message { + padding: ${dt('autocomplete.empty.message.padding')}; +} + +.p-autocomplete-fluid { + display: flex; +} + +.p-autocomplete-fluid:has(.p-autocomplete-dropdown) .p-autocomplete-input { + width: 1%; +} + +/* For PrimeNG */ +p-autocomplete.ng-invalid.ng-dirty > .p-autocomplete.p-inputwrapper > .p-autocomplete-input.p-inputtext { + border-color: ${dt('autocomplete.invalid.border.color')}; +} + +p-autocomplete.ng-invalid.ng-dirty > .p-autocomplete.p-inputwrapper > .p-autocomplete-input-multiple { + border-color: ${dt('autocomplete.invalid.border.color')}; +} + +.p-autocomplete-clear-icon { + position: absolute; + top: 50%; + margin-top: -0.5rem; + cursor: pointer; + right: ${dt('autocomplete.padding.x')}; + color: ${dt('autocomplete.dropdown.color')}; +} +`; + +const inlineStyles = { + root: { position: 'relative' } +}; + +const classes = { + root: ({ instance }) => ({ + 'p-autocomplete p-component p-inputwrapper': true, + 'p-disabled': instance.disabled, + 'p-focus': instance.focused, + 'p-inputwrapper-filled': instance.filled, + 'p-inputwrapper-focus': (instance.focused && !instance.disabled) || instance.autofocus || instance.overlayVisible, + 'p-autocomplete-open': instance.overlayVisible, + 'p-autocomplete-clearable': instance.showClear && !instance.disabled + // 'p-invalid': instance.invalid, + // 'p-autocomplete-fluid': instance.fluid + }), + pcInput: 'p-autocomplete-input', + inputMultiple: ({ instance }) => ({ + 'p-autocomplete-input-multiple': true, + 'p-variant-filled': instance.variant ? instance.variant === 'filled' : instance.config.inputStyle() === 'filled' + }), + chipItem: ({ instance, i }) => [ + 'p-autocomplete-chip-item', + { + 'p-focus': instance.focusedMultipleOptionIndex === i + } + ], + pcChip: 'p-autocomplete-chip', + chipIcon: 'p-autocomplete-chip-icon', + inputChip: 'p-autocomplete-input-chip', + loader: 'p-autocomplete-loader', + dropdown: 'p-autocomplete-dropdown', + overlay: 'p-autocomplete-overlay p-component', + list: 'p-autocomplete-list', + optionGroup: 'p-autocomplete-option-group', + option: ({ instance, option, i, getItemOptions }) => ({ + 'p-autocomplete-option': true, + 'p-autocomplete-option-selected': instance.isSelected(option), + 'p-focus': instance.focusedOptionIndex === instance.getOptionIndex(i, getItemOptions), + 'p-disabled': instance.isOptionDisabled(option) + }), + emptyMessage: 'p-autocomplete-empty-message' +}; + +@Injectable() +export class AutoCompleteStyle extends BaseStyle { + name = 'autocomplete'; + + theme = theme; + + classes = classes; + + inlineStyles = inlineStyles; +}