diff --git a/api-generator/components/autocomplete.js b/api-generator/components/autocomplete.js index 825e2aa60..888298632 100644 --- a/api-generator/components/autocomplete.js +++ b/api-generator/components/autocomplete.js @@ -179,6 +179,12 @@ const AutoCompleteProps = [ default: "true", description: "Whether to focus on the first visible or selected element when the overlay panel is shown." }, + { + name: "selectOnFocus", + type: "boolean", + default: "false", + description: "When enabled, the focused option is selected." + }, { name: "searchLocale", type: "string", diff --git a/api-generator/components/cascadeselect.js b/api-generator/components/cascadeselect.js index cf188cf63..e3a79c774 100644 --- a/api-generator/components/cascadeselect.js +++ b/api-generator/components/cascadeselect.js @@ -119,6 +119,12 @@ const CascadeSelectProps = [ default: "true", description: "Whether to focus on the first visible or selected element when the overlay panel is shown." }, + { + name: "selectOnFocus", + type: "boolean", + default: "false", + description: "When enabled, the focused option is selected/opened." + }, { name: "searchLocale", type: "string", diff --git a/api-generator/components/dropdown.js b/api-generator/components/dropdown.js index a6f86c0e0..8f132fcf2 100644 --- a/api-generator/components/dropdown.js +++ b/api-generator/components/dropdown.js @@ -191,6 +191,12 @@ const DropdownProps = [ default: "true", description: "Whether to focus on the first visible or selected element when the overlay panel is shown." }, + { + name: "selectOnFocus", + type: "boolean", + default: "false", + description: "When enabled, the focused option is selected." + }, { name: "filterMessage", type: "string", diff --git a/api-generator/components/listbox.js b/api-generator/components/listbox.js index dc1f84bce..4741ab862 100644 --- a/api-generator/components/listbox.js +++ b/api-generator/components/listbox.js @@ -119,6 +119,12 @@ const ListboxProps = [ default: "true", description: "Whether to focus on the first visible or selected element." }, + { + name: "selectOnFocus", + type: "boolean", + default: "false", + description: "When enabled, the focused option is selected." + }, { name: "filterMessage", type: "string", diff --git a/src/components/autocomplete/AutoComplete.d.ts b/src/components/autocomplete/AutoComplete.d.ts index 3d28711dd..0c69cc4c3 100755 --- a/src/components/autocomplete/AutoComplete.d.ts +++ b/src/components/autocomplete/AutoComplete.d.ts @@ -202,6 +202,11 @@ export interface AutoCompleteProps { * Default value is true. */ autoOptionFocus?: boolean | undefined; + /** + * When enabled, the focused option is selected. + * Default value is false. + */ + selectOnFocus?: boolean | undefined; /** * Locale to use in searching. The default locale is the host environment's current locale. */ diff --git a/src/components/autocomplete/AutoComplete.vue b/src/components/autocomplete/AutoComplete.vue index 4055ea5d6..31782caab 100755 --- a/src/components/autocomplete/AutoComplete.vue +++ b/src/components/autocomplete/AutoComplete.vue @@ -95,7 +95,7 @@ export default { type: String, default: 'blank' }, - autoHighlight: { // TODO: Deprecated since v3.16.0 + autoHighlight: { // TODO: Deprecated since v3.16.0. Use selectOnFocus property instead. type: Boolean, default: false }, @@ -154,6 +154,10 @@ export default { type: Boolean, default: true }, + selectOnFocus: { + type: Boolean, + default: false + }, searchLocale: { type: String, default: undefined @@ -193,7 +197,6 @@ export default { overlay: null, virtualScroller: null, searchTimeout: null, - selectOnFocus: false, focusOnHover: false, dirty: false, data() { @@ -464,7 +467,7 @@ export default { this.$emit('dropdown-click', { originalEvent: event, query }); }, - onOptionSelect(event, option) { + onOptionSelect(event, option, isHide = true) { const value = this.getOptionValue(option); if (this.multiple) { @@ -480,7 +483,7 @@ export default { this.$emit('item-select', { originalEvent: event, value: option }); - this.hide(true); + isHide && this.hide(true); }, onOptionMouseMove(event, index) { if (this.focusOnHover) { @@ -788,7 +791,7 @@ export default { this.scrollInView(); if (this.selectOnFocus || this.autoHighlight) { - this.updateModel(event, this.getOptionValue(this.visibleOptions[index])); + this.onOptionSelect(event, this.visibleOptions[index], false); } } }, @@ -807,8 +810,7 @@ export default { autoUpdateModel() { if ((this.selectOnFocus || this.autoHighlight) && this.autoOptionFocus && !this.hasSelectedOption) { this.focusedOptionIndex = this.findFirstFocusedOptionIndex(); - const value = this.getOptionValue(this.visibleOptions[this.focusedOptionIndex]); - this.updateModel(null, this.multiple ? [value] : value); + this.onOptionSelect(null, this.visibleOptions[this.focusedOptionIndex], false); } }, updateModel(event, value) { diff --git a/src/components/cascadeselect/CascadeSelect.d.ts b/src/components/cascadeselect/CascadeSelect.d.ts index 0219e76ca..9c6649f32 100644 --- a/src/components/cascadeselect/CascadeSelect.d.ts +++ b/src/components/cascadeselect/CascadeSelect.d.ts @@ -121,6 +121,11 @@ export interface CascadeSelectProps { * Default value is true. */ autoOptionFocus?: boolean | undefined; + /** + * When enabled, the focused option is selected/opened. + * Default value is false. + */ + selectOnFocus?: boolean | undefined; /** * Locale to use in searching. The default locale is the host environment's current locale. */ diff --git a/src/components/cascadeselect/CascadeSelect.vue b/src/components/cascadeselect/CascadeSelect.vue index a05ca63ef..9fcbd7978 100644 --- a/src/components/cascadeselect/CascadeSelect.vue +++ b/src/components/cascadeselect/CascadeSelect.vue @@ -79,6 +79,10 @@ export default { type: Boolean, default: true }, + selectOnFocus: { + type: Boolean, + default: false + }, searchLocale: { type: String, default: undefined @@ -122,7 +126,6 @@ export default { overlay: null, searchTimeout: null, searchValue: null, - selectOnFocus: false, focusOnHover: false, data() { return { @@ -141,6 +144,8 @@ export default { }, mounted() { this.id = this.$attrs.id || this.id; + + this.autoUpdateModel(); }, beforeUnmount() { this.unbindOutsideClickListener(); @@ -185,7 +190,7 @@ export default { show(isFocus) { this.$emit('before-show'); this.overlayVisible = true; - this.activeOptionPath = this.findOptionPathByValue(this.modelValue); + this.activeOptionPath = this.hasSelectedOption ? this.findOptionPathByValue(this.modelValue) : this.activeOptionPath; if (this.hasSelectedOption && ObjectUtils.isNotEmpty(this.activeOptionPath)) { const processedOption = this.activeOptionPath[this.activeOptionPath.length - 1]; @@ -286,7 +291,10 @@ export default { } }, onOptionChange(event) { - const { originalEvent, processedOption, isFocus } = event; + const { originalEvent, processedOption, isFocus, isHide } = event; + + if (ObjectUtils.isEmpty(processedOption)) return; + const { index, level, parentKey, children } = processedOption; const grouped = ObjectUtils.isNotEmpty(children); @@ -296,15 +304,15 @@ export default { this.focusedOptionInfo = { index, level, parentKey }; this.activeOptionPath = activeOptionPath; - grouped ? this.onOptionGroupSelect(originalEvent, processedOption) : this.onOptionSelect(originalEvent, processedOption); + grouped ? this.onOptionGroupSelect(originalEvent, processedOption) : this.onOptionSelect(originalEvent, processedOption, isHide); isFocus && this.$refs.focusInput.focus(); }, - onOptionSelect(event, processedOption) { + onOptionSelect(event, processedOption, isHide = true) { const value = this.getOptionValue(processedOption.option); this.activeOptionPath.forEach(p => p.selected = true); this.updateModel(event, value); - this.hide(true); + isHide && this.hide(true); }, onOptionGroupSelect(event, processedOption) { this.dirty = true; @@ -634,7 +642,7 @@ export default { this.scrollInView(); if (this.selectOnFocus) { - this.updateModel(event, this.getOptionValue(this.visibleOptions[index])); + this.onOptionChange({ originalEvent: event, processedOption: this.visibleOptions[index], isHide: false }); } } }, @@ -648,8 +656,9 @@ export default { autoUpdateModel() { if (this.selectOnFocus && this.autoOptionFocus && !this.hasSelectedOption) { this.focusedOptionInfo.index = this.findFirstFocusedOptionIndex(); - const value = this.getOptionValue(this.visibleOptions[this.focusedOptionInfo.index]); - this.updateModel(null, value); + this.onOptionChange({ processedOption: this.visibleOptions[this.focusedOptionInfo.index], isHide: false }); + + !this.overlayVisible && (this.focusedOptionInfo = { index: -1, level: 0, parentKey: '' }); } }, updateModel(event, value) { @@ -713,7 +722,7 @@ export default { if (this.hasSelectedOption) { const activeOptionPath = this.findOptionPathByValue(this.modelValue); - const processedOption = activeOptionPath.length ? activeOptionPath[activeOptionPath.length - 1] : null; + const processedOption = ObjectUtils.isNotEmpty(activeOptionPath) ? activeOptionPath[activeOptionPath.length - 1] : null; return processedOption ? this.getOptionLabel(processedOption.option) : label; } diff --git a/src/components/dropdown/Dropdown.d.ts b/src/components/dropdown/Dropdown.d.ts index adaf626b8..fb34f5bf3 100755 --- a/src/components/dropdown/Dropdown.d.ts +++ b/src/components/dropdown/Dropdown.d.ts @@ -178,6 +178,11 @@ export interface DropdownProps { * Default value is true. */ autoOptionFocus?: boolean | undefined; + /** + * When enabled, the focused option is selected. + * Default value is false. + */ + selectOnFocus?: boolean | undefined; /** * Text to be displayed in hidden accessible field when filtering returns any results. Defaults to value from PrimeVue locale configuration. * Default value is '{0} results are available'. diff --git a/src/components/dropdown/Dropdown.vue b/src/components/dropdown/Dropdown.vue index a0dd969b7..863b00f02 100755 --- a/src/components/dropdown/Dropdown.vue +++ b/src/components/dropdown/Dropdown.vue @@ -140,6 +140,10 @@ export default { type: Boolean, default: true }, + selectOnFocus: { + type: Boolean, + default: false + }, filterMessage: { type: String, default: null @@ -182,7 +186,6 @@ export default { searchTimeout: null, searchValue: null, isModelValueChanged: false, - selectOnFocus: false, focusOnHover: false, data() { return { @@ -391,11 +394,11 @@ export default { onLastHiddenFocus() { this.$refs.firstHiddenFocusableElementOnOverlay.focus(); }, - onOptionSelect(event, option) { + onOptionSelect(event, option, isHide = true) { const value = this.getOptionValue(option); this.updateModel(event, value); - this.hide(true); + isHide && this.hide(true); }, onOptionMouseMove(event, index) { if (this.focusOnHover) { @@ -744,7 +747,7 @@ export default { this.scrollInView(); if (this.selectOnFocus) { - this.updateModel(event, this.getOptionValue(this.visibleOptions[index])); + this.onOptionSelect(event, this.visibleOptions[index], false); } } }, @@ -763,8 +766,7 @@ export default { autoUpdateModel() { if (this.selectOnFocus && this.autoOptionFocus && !this.hasSelectedOption) { this.focusedOptionIndex = this.findFirstFocusedOptionIndex(); - const value = this.getOptionValue(this.visibleOptions[this.focusedOptionIndex]); - this.updateModel(null, value); + this.onOptionSelect(null, this.visibleOptions[this.focusedOptionIndex], false); } }, updateModel(event, value) { diff --git a/src/components/listbox/Listbox.d.ts b/src/components/listbox/Listbox.d.ts index 1721ab87b..76e60ec58 100755 --- a/src/components/listbox/Listbox.d.ts +++ b/src/components/listbox/Listbox.d.ts @@ -122,6 +122,11 @@ export interface ListboxProps { * Default value is true. */ autoOptionFocus?: boolean | undefined; + /** + * When enabled, the focused option is selected. + * Default value is false. + */ + selectOnFocus?: boolean | undefined; /** * Text to be displayed in hidden accessible field when filtering returns any results. Defaults to value from PrimeVue locale configuration. * Default value is '{0} results are available'. diff --git a/src/components/listbox/Listbox.vue b/src/components/listbox/Listbox.vue index d8eea3423..8460e59e0 100755 --- a/src/components/listbox/Listbox.vue +++ b/src/components/listbox/Listbox.vue @@ -96,6 +96,10 @@ export default { type: Boolean, default: true }, + selectOnFocus: { + type: Boolean, + default: false + }, filterMessage: { type: String, default: null @@ -135,7 +139,6 @@ export default { startRangeIndex: -1, searchTimeout: null, searchValue: '', - selectOnFocus: false, focusOnHover: false, data() { return { @@ -598,7 +601,7 @@ export default { this.scrollInView(); if (this.selectOnFocus && !this.multiple) { - this.updateModel(event, this.getOptionValue(this.visibleOptions[index])); + this.onOptionSelect(event, this.visibleOptions[index]); } } }, @@ -613,10 +616,9 @@ export default { } }, autoUpdateModel() { - if (this.selectOnFocus && this.autoOptionFocus && !this.hasSelectedOption) { + if (this.selectOnFocus && this.autoOptionFocus && !this.hasSelectedOption && !this.multiple) { this.focusedOptionIndex = this.findFirstFocusedOptionIndex(); - const value = this.getOptionValue(this.visibleOptions[this.focusedOptionIndex]); - this.updateModel(null, this.multiple ? [value] : value); + this.onOptionSelect(null, this.visibleOptions[this.focusedOptionIndex]); } }, updateModel(event, value) { diff --git a/src/views/autocomplete/AutoCompleteDoc.vue b/src/views/autocomplete/AutoCompleteDoc.vue index 4845a2300..6e9e8dae5 100755 --- a/src/views/autocomplete/AutoCompleteDoc.vue +++ b/src/views/autocomplete/AutoCompleteDoc.vue @@ -328,6 +328,12 @@ export default {