From 7f446cf360466061227374ab94e4cf012511f651 Mon Sep 17 00:00:00 2001 From: mertsincan Date: Fri, 29 Jul 2022 05:18:45 +0100 Subject: [PATCH] Fixed #2814 - Improve MultiSelect implementation for Accessibility --- api-generator/components/multiselect.js | 238 +++-- src/components/multiselect/MultiSelect.d.ts | 158 ++- src/components/multiselect/MultiSelect.vue | 1006 ++++++++++++------- src/views/multiselect/MultiSelectDoc.vue | 485 +++++++-- 4 files changed, 1327 insertions(+), 560 deletions(-) diff --git a/api-generator/components/multiselect.js b/api-generator/components/multiselect.js index 0ee9aa57e..9cf553167 100644 --- a/api-generator/components/multiselect.js +++ b/api-generator/components/multiselect.js @@ -47,6 +47,66 @@ const MultiSelectProps = [ default: "200px", description: "Height of the viewport, a scrollbar is defined if height of list exceeds this value." }, + { + name: "placeholder", + type: "string", + default: "null", + description: "Label to display when there are no selections." + }, + { + name: "disabled", + type: "boolean", + default: "false", + description: "When present, it specifies that the component should be disabled." + }, + { + name: "inputId", + type: "string", + default: "null", + description: "Identifier of the underlying input element." + }, + { + name: "inputProps", + type: "object", + default: "null", + description: "Uses to pass all properties of the HTMLInputElement to the focusable input element inside the component." + }, + { + name: "panelStyle", + type: "object", + default: "null", + description: "Inline style of the overlay panel." + }, + { + name: "panelClass", + type: "string", + default: "null", + description: "Style class of the overlay panel." + }, + { + name: "panelProps", + type: "object", + default: "null", + description: "Uses to pass all properties of the HTMLDivElement to the overlay panel." + }, + { + name: "filterInputProps", + type: "object", + default: "null", + description: "Uses to pass all properties of the HTMLInputElement to the filter input inside the overlay panel." + }, + { + name: "closeButtonProps", + type: "object", + default: "null", + description: "Uses to pass all properties of the HTMLButtonElement to the close button inside the overlay panel." + }, + { + name: "dataKey", + type: "string", + default: "null", + description: "A property to uniquely identify an option." + }, { name: "filter", type: "boolean", @@ -78,52 +138,10 @@ const MultiSelectProps = [ description: "Fields used when filtering the options, defaults to optionLabel." }, { - name: "placeholder", + name: "appendTo", type: "string", - default: "null", - description: "Label to display when there are no selections." - }, - { - name: "disabled", - type: "boolean", - default: "false", - description: "When present, it specifies that the component should be disabled." - }, - { - name: "tabindex", - type: "string", - default: "null", - description: "Index of the element in tabbing order." - }, - { - name: "inputId", - type: "string", - default: "null", - description: "Identifier of the underlying input element." - }, - { - name: "dataKey", - type: "string", - default: "null", - description: "A property to uniquely identify an option." - }, - { - name: "ariaLabelledBy", - type: "string", - default: "null", - description: 'A valid query selector or an HTMLElement to specify where the overlay gets attached. Special keywords are "body" for document body and "self" for the element itself.' - }, - { - name: "emptyFilterMessage", - type: "string", - default: "No results found", - description: "Text to display when filtering does not return any results. Defaults to value from PrimeVue locale configuration." - }, - { - name: "emptyMessage", - type: "string", - default: "No results found", - description: "Text to display when there are no options available. Defaults to value from PrimeVue locale configuration." + default: "body", + description: "A valid query selector or an HTMLElement to specify where the overlay gets attached. Special keywords are 'body' for document body and 'self' for the element itself." }, { name: "display", @@ -132,10 +150,16 @@ const MultiSelectProps = [ description: 'Defines how the selected items are displayed, valid values are "comma" and "chip".' }, { - name: "panelClass", + name: "selectedItemsLabel", type: "string", + default: "{0} items selected", + description: "Label to display after exceeding max selected labels." + }, + { + name: "maxSelectedLabels", + type: "number", default: "null", - description: "Style class of the overlay panel." + description: "Decides how many selected item labels to show at most." }, { name: "selectionLimit", @@ -161,18 +185,6 @@ const MultiSelectProps = [ default: "pi pi-spinner pi-spin", description: "Icon to display in loading state." }, - { - name: "maxSelectedLabels", - type: "number", - default: "null", - description: "Decides how many selected item labels to show at most." - }, - { - name: "selectedItemsLabel", - type: "string", - default: "{0} items selected", - description: "Label to display after exceeding max selected labels." - }, { name: "selectAll", type: "boolean", @@ -191,6 +203,60 @@ const MultiSelectProps = [ default: "null", description: "Whether to use the virtualScroller feature. The properties of VirtualScroller component can be used like an object in it." }, + { + name: "autoOptionFocus", + type: "boolean", + default: "true", + description: "Whether to focus on the first visible or selected element when the overlay panel is shown." + }, + { + name: "filterMessage", + type: "string", + default: "{0} results are available", + description: "Text to be displayed in hidden accessible field when filtering returns any results. Defaults to value from PrimeVue locale configuration." + }, + { + name: "selectionMessage", + type: "string", + default: "{0} items selected", + description: "Text to be displayed in hidden accessible field when options are selected. Defaults to value from PrimeVue locale configuration." + }, + { + name: "emptySelectionMessage", + type: "string", + default: "No selected item", + description: "Text to be displayed in hidden accessible field when any option is not selected. Defaults to value from PrimeVue locale configuration." + }, + { + name: "emptyFilterMessage", + type: "string", + default: "No results found", + description: "Text to display when filtering does not return any results. Defaults to value from PrimeVue locale configuration." + }, + { + name: "emptyMessage", + type: "string", + default: "No results found", + description: "Text to display when there are no options available. Defaults to value from PrimeVue locale configuration." + }, + { + name: "tabindex", + type: "number", + default: "null", + description: "Index of the element in tabbing order." + }, + { + name: "ariaLabel", + type: "string", + default: "null", + description: "Defines a string value that labels an interactive element." + } + { + name: "ariaLabelledby", + type: "string", + default: "null", + description: "Identifier of the underlying input element." + } ]; const MultiSelectEvents = [ @@ -210,6 +276,28 @@ const MultiSelectEvents = [ } ] }, + { + name: "focus", + description: "Callback to invoke when component receives focus.", + arguments: [ + { + name: "event", + type: "object", + description: "Browser event" + } + ] + }, + { + name: "blur", + description: "Callback to invoke when component loses focus.", + arguments: [ + { + name: "event", + type: "object", + description: "Browser event" + } + ] + }, { name: "before-show", description: "Callback to invoke before the overlay is shown." @@ -261,18 +349,18 @@ const MultiSelectEvents = [ ]; const MultiSelectSlots = [ - { - name: "option", - description: "Custom content for the item's option" - }, - { - name: "optiongroup", - description: "Custom content for the item's optiongroup" - }, { name: "value", description: "Custom content for the item value" }, + { + name: "chip", + description: "Custom content for the chip display." + }, + { + name: "indicator", + description: "Custom content for the dropdown indicator" + }, { name: "header", description: "Custom content for the component's header" @@ -281,6 +369,14 @@ const MultiSelectSlots = [ name: "footer", description: "Custom content for the component's footer" }, + { + name: "option", + description: "Custom content for the item's option" + }, + { + name: "optiongroup", + description: "Custom content for the item's optiongroup" + }, { name: "emptyfilter", description: "Custom content when there is no filtered data to display" @@ -289,10 +385,6 @@ const MultiSelectSlots = [ name: "empty", description: "Custom content when there is no data to display" }, - { - name: "chip", - description: "Custom content for the chip display." - }, { name: "content", description: "Custom content for the virtual scroller" @@ -300,10 +392,6 @@ const MultiSelectSlots = [ { name: "loader", description: "Custom content for the virtual scroller loader items" - }, - { - name: "indicator", - description: "Custom content for the dropdown indicator" } ]; diff --git a/src/components/multiselect/MultiSelect.d.ts b/src/components/multiselect/MultiSelect.d.ts index aeb0d8ed3..b46ddc1c1 100755 --- a/src/components/multiselect/MultiSelect.d.ts +++ b/src/components/multiselect/MultiSelect.d.ts @@ -2,13 +2,13 @@ import { VNode } from 'vue'; import { ClassComponent, GlobalComponentConstructor } from '../ts-helpers'; import { VirtualScrollerProps, VirtualScrollerItemOptions } from '../virtualscroller'; -type MultiSelectOptionLabelType = string | ((data: any) => string) | undefined; +type MultiSelectOptionLabelType = string | ((data: any) => string) | undefined; -type MultiSelectOptionValueType = string | ((data: any) => any) | undefined; +type MultiSelectOptionValueType = string | ((data: any) => any) | undefined; -type MultiSelectOptionDisabledType = string | ((data: any) => boolean) | undefined; +type MultiSelectOptionDisabledType = string | ((data: any) => boolean) | undefined; -type MultiSelectOptionChildrenType = string | ((data: any) => any[]) | undefined; +type MultiSelectOptionChildrenType = string | ((data: any) => any[]) | undefined; type MultiSelectFilterMatchModeType = 'contains' | 'startsWith' | 'endsWith' | undefined; @@ -49,7 +49,7 @@ export interface MultiSelectFilterEvent { value: string; } -export interface MultiSelectProps { +export interface MultiSelectProps extends HTMLDivElement { /** * Value of the component. */ @@ -91,14 +91,34 @@ export interface MultiSelectProps { * When present, it specifies that the component should be disabled. */ disabled?: boolean | undefined; - /** - * Index of the element in tabbing order. - */ - tabindex?: string | number | undefined; /** * Identifier of the underlying input element. */ inputId?: string | undefined; + /** + * Uses to pass all properties of the HTMLInputElement to the focusable input element inside the component. + */ + inputProps?: HTMLInputElement | undefined; + /** + * Inline style of the overlay panel. + */ + panelStyle?: any; + /** + * Style class of the overlay panel. + */ + panelClass?: any; + /** + * Uses to pass all properties of the HTMLDivElement to the overlay panel. + */ + panelProps?: HTMLDivElement | undefined; + /** + * Uses to pass all properties of the HTMLInputElement to the filter input inside the overlay panel. + */ + filterInputProps?: HTMLInputElement | undefined; + /** + * Uses to pass all properties of the HTMLButtonElement to the clear button inside the overlay panel. + */ + closeButtonProps?: HTMLButtonElement | undefined; /** * A property to uniquely identify an option. */ @@ -125,36 +145,18 @@ export interface MultiSelectProps { * Fields used when filtering the options, defaults to optionLabel. */ filterFields?: string[] | undefined; - /** - * Establishes relationships between the component and label(s) where its value should be one or more element IDs. - */ - ariaLabelledBy?: string | undefined; /** * A valid query selector or an HTMLElement to specify where the overlay gets attached. Special keywords are 'body' for document body and 'self' for the element itself. * @see MultiSelectAppendToType * Default value is 'body'. */ appendTo?: MultiSelectAppendToType; - /** - * Text to display when filtering does not return any results. Defaults to value from PrimeVue locale configuration. - * Default value is 'No results found'. - */ - emptyFilterMessage?: string | undefined; - /** - * Text to display when there are no options available. Defaults to value from PrimeVue locale configuration. - * Default value is 'No results found'. - */ - emptyMessage?: string | undefined; /** * Defines how the selected items are displayed. * @see MultiSelectDisplayType * Default value is 'comma'. */ display?: MultiSelectDisplayType; - /** - * Style class of the overlay panel. - */ - panelClass?: any; /** * Label to display after exceeding max selected labels. * Default value is '{0} items selected'. @@ -195,6 +197,48 @@ export interface MultiSelectProps { * @see VirtualScroller.VirtualScrollerProps */ virtualScrollerOptions?: VirtualScrollerProps; + /** + * Whether to focus on the first visible or selected element when the overlay panel is shown. + * Default value is true. + */ + autoOptionFocus?: 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'. + */ + filterMessage?: string | undefined; + /** + * Text to be displayed in hidden accessible field when options are selected. Defaults to value from PrimeVue locale configuration. + * Default value is '{0} items selected'. + */ + selectionMessage?: string | undefined; + /** + * Text to be displayed in hidden accessible field when any option is not selected. Defaults to value from PrimeVue locale configuration. + * Default value is 'No selected item'. + */ + emptySelectionMessage?: string | undefined; + /** + * Text to display when filtering does not return any results. Defaults to value from PrimeVue locale configuration. + * Default value is 'No results found'. + */ + emptyFilterMessage?: string | undefined; + /** + * Text to display when there are no options available. Defaults to value from PrimeVue locale configuration. + * Default value is 'No results found'. + */ + emptyMessage?: string | undefined; + /** + * Index of the element in tabbing order. + */ + tabindex?: number | string | undefined; + /** + * Defines a string value that labels an interactive element. + */ + ariaLabel?: string | undefined; + /** + * Identifier of the underlying input element. + */ + ariaLabelledby?: string | undefined; } export interface MultiSelectSlots { @@ -212,6 +256,20 @@ export interface MultiSelectSlots { */ placeholder: string; }) => VNode[]; + /** + * Custom chip template. + * @param {Object} scope - chip slot's params. + */ + chip: (scope: { + /** + * A value in the selection + */ + value: any; + }) => VNode[]; + /** + * Custom indicator template. + */ + indicator: () => VNode[]; /** * Custom header template. * @param {Object} scope - header slot's params. @@ -240,14 +298,6 @@ export interface MultiSelectSlots { */ options: any[]; }) => VNode[]; - /** - * Custom emptyfilter template. - */ - emptyfilter: () => VNode[]; - /** - * Custom empty template. - */ - empty: () => VNode[]; /** * Custom option template. * @param {Object} scope - option slot's params. @@ -277,15 +327,13 @@ export interface MultiSelectSlots { index: number; }) => VNode[]; /** - * Custom chip template. - * @param {Object} scope - chip slot's params. + * Custom emptyfilter template. */ - chip: (scope: { - /** - * A value in the selection - */ - value: any; - }) => VNode[]; + emptyfilter: () => VNode[]; + /** + * Custom empty template. + */ + empty: () => VNode[]; /** * Custom content template. * @param {Object} scope - content slot's params. @@ -321,10 +369,6 @@ export interface MultiSelectSlots { */ options: any[]; }) => VNode[]; - /** - * Custom indicator template. - */ - indicator: () => VNode[]; } export declare type MultiSelectEmits = { @@ -338,6 +382,16 @@ export declare type MultiSelectEmits = { * @param {MultiSelectChangeEvent} event - Custom change event. */ 'change': (event: MultiSelectChangeEvent) => void; + /** + * Callback to invoke when the component receives focus. + * @param {Event} event - Browser event. + */ + 'focus': (event: Event) => void; + /** + * Callback to invoke when the component loses focus. + * @param {Event} event - Browser event. + */ + 'blur': (event: Event) => void; /** * Callback to invoke before the overlay is shown. */ @@ -369,12 +423,18 @@ export declare type MultiSelectEmits = { declare class MultiSelect extends ClassComponent { /** * Shows the overlay. + * @param {boolean} [isFocus] - Decides whether to focus on the component. Default value is false. + * + * @memberof MultiSelect */ - show: () => void; + show: (isFocus?: boolean) => void; /** * Hides the overlay. + * @param {boolean} [isFocus] - Decides whether to focus on the component. Default value is false. + * + * @memberof MultiSelect */ - hide: () => void; + hide: (isFocus?: boolean) => void; } declare module '@vue/runtime-core' { diff --git a/src/components/multiselect/MultiSelect.vue b/src/components/multiselect/MultiSelect.vue index c441e3478..169203d4f 100755 --- a/src/components/multiselect/MultiSelect.vue +++ b/src/components/multiselect/MultiSelect.vue @@ -1,8 +1,10 @@ @@ -24,37 +26,48 @@
- +
-
+
+ -
-