Skip to content

Commit

Permalink
add multiselect functionality to listbox (microsoft#5530)
Browse files Browse the repository at this point in the history
* add multiselect functionality to listbox

* Change files

* add comment

* rename ariaMultselectable to ariaMultiSelectable

* remove empty default case

* reduce repetition for notifier

* update documentation and specs
  • Loading branch information
radium-v authored Feb 3, 2022
1 parent 02ba401 commit 45116d1
Show file tree
Hide file tree
Showing 16 changed files with 903 additions and 58 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "add multiselect functionality to listbox",
"packageName": "@microsoft/fast-components",
"email": "john.kreitlow@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "add multiselect functionality to listbox",
"packageName": "@microsoft/fast-foundation",
"email": "john.kreitlow@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
# fast-listbox
An implementation of a [listbox](https://w3c.github.io/aria/#listbox).

An implementation of a [listbox](https://www.w3.org/TR/wai-aria-practices-1.2/#Listbox).
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<h1>Listbox</h1>
<h1>Listbox in single-selection mode</h1>

<h2>Default</h2>
<fast-listbox id="listbox">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<h1>Listbox in multiple-selection mode</h1>

<h2>Default</h2>
<fast-listbox id="listbox-multiple" multiple>
<fast-option>William Hartnell</fast-option>
<fast-option>Patrick Troughton</fast-option>
<fast-option>Jon Pertwee</fast-option>
<fast-option>Tom Baker</fast-option>
<fast-option>Peter Davidson</fast-option>
<fast-option>Colin Baker</fast-option>
<fast-option>Sylvester McCoy</fast-option>
<fast-option>Paul McGann</fast-option>
<fast-option>Christopher Eccleston</fast-option>
<fast-option>David Tenant</fast-option>
<fast-option>Matt Smith</fast-option>
<fast-option>Peter Capaldi</fast-option>
<fast-option>Jodie Whittaker</fast-option>
</fast-listbox>

<h2>Listboxes with size attribute</h2>
<fast-listbox id="listbox-with-size-1" size="1" multiple>
<fast-option>option 1</fast-option>
<fast-option>option 2</fast-option>
<fast-option>option 3</fast-option>
<fast-option>option 4</fast-option>
<fast-option>option 5</fast-option>
</fast-listbox>
<fast-listbox id="listbox-with-size-2" size="2" multiple>
<fast-option>option 1</fast-option>
<fast-option>option 2</fast-option>
<fast-option>option 3</fast-option>
<fast-option>option 4</fast-option>
<fast-option>option 5</fast-option>
</fast-listbox>
<fast-listbox id="listbox-with-size-3" size="3" multiple>
<fast-option>option 1</fast-option>
<fast-option>option 2</fast-option>
<fast-option>option 3</fast-option>
<fast-option>option 4</fast-option>
<fast-option>option 5</fast-option>
</fast-listbox>
<fast-listbox id="listbox-with-size-4" size="4" multiple>
<fast-option>option 1</fast-option>
<fast-option>option 2</fast-option>
<fast-option>option 3</fast-option>
<fast-option>option 4</fast-option>
<fast-option>option 5</fast-option>
</fast-listbox>
<fast-listbox id="listbox-with-size-5" size="5" multiple>
<fast-option>option 1</fast-option>
<fast-option>option 2</fast-option>
<fast-option>option 3</fast-option>
<fast-option>option 4</fast-option>
<fast-option>option 5</fast-option>
</fast-listbox>

<h2>Listbox with a default selected item</h2>
<fast-listbox id="listbox-with-default" multiple>
<fast-option>John</fast-option>
<fast-option>Paul</fast-option>
<fast-option selected>George</fast-option>
<fast-option>Ringo</fast-option>
</fast-listbox>

<fast-listbox id="listbox-with-two-default-selected" multiple>
<fast-option>John</fast-option>
<fast-option selected>Paul</fast-option>
<fast-option selected>George</fast-option>
<fast-option>Ringo</fast-option>
</fast-listbox>

<h2>Listbox with disabled items</h2>
<fast-listbox id="listbox-with-every-other-disabled" multiple>
<fast-option>Extra Small</fast-option>
<fast-option disabled>Small</fast-option>
<fast-option>Medium</fast-option>
<fast-option disabled>Large</fast-option>
<fast-option>Extra Large</fast-option>
</fast-listbox>
<fast-listbox id="listbox-with-adjacent-disabled-start" multiple>
<fast-option disabled>Extra Small</fast-option>
<fast-option disabled>Small</fast-option>
<fast-option>Medium</fast-option>
<fast-option>Large</fast-option>
<fast-option>Extra Large</fast-option>
</fast-listbox>
<fast-listbox id="listbox-with-adjacent-disabled-middle" multiple>
<fast-option>Extra Small</fast-option>
<fast-option disabled>Small</fast-option>
<fast-option disabled>Medium</fast-option>
<fast-option>Large</fast-option>
<fast-option>Extra Large</fast-option>
</fast-listbox>
<fast-listbox id="listbox-with-adjacent-disabled-end" multiple>
<fast-option>Extra Small</fast-option>
<fast-option>Small</fast-option>
<fast-option>Medium</fast-option>
<fast-option disabled>Large</fast-option>
<fast-option disabled>Extra Large</fast-option>
</fast-listbox>
<fast-listbox id="listbox-with-all-but-one-disabled" multiple>
<fast-option disabled>Extra Small</fast-option>
<fast-option disabled>Small</fast-option>
<fast-option disabled>Medium</fast-option>
<fast-option disabled>Large</fast-option>
<fast-option>Extra Large</fast-option>
</fast-listbox>
<fast-listbox id="listbox-with-all-disabled" multiple>
<fast-option disabled>Extra Small</fast-option>
<fast-option disabled>Small</fast-option>
<fast-option disabled>Medium</fast-option>
<fast-option disabled>Large</fast-option>
<fast-option disabled>Extra Large</fast-option>
</fast-listbox>

<h2>Empty Listbox</h2>
<fast-listbox id="listbox-empty" multiple></fast-listbox>
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import Base from "./fixtures/base.html";
import Multiselect from "./fixtures/multiselect.html";

export default {
title: "Listbox",
};

export const Listbox = () => Base;
export const ListboxMultiselect = () => Multiselect;
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@
"default": false,
"required": false
},
{
"name": "multiple",
"title": "Multiple",
"description": "Indicates if the listbox is in multi-selection mode",
"type": "boolean",
"default": false,
"required": false
},
{
"name": "size",
"title": "Size",
Expand Down
65 changes: 62 additions & 3 deletions packages/web-components/fast-foundation/docs/api-report.md
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,7 @@ export class DelegatesARIAListbox {
ariaActiveDescendant: string;
ariaDisabled: "true" | "false";
ariaExpanded: "true" | "false" | undefined;
ariaMultiSelectable: "true" | "false" | undefined;
}

// @internal
Expand Down Expand Up @@ -1348,22 +1349,34 @@ export abstract class Listbox extends FoundationElement {
// @internal
get firstSelectedOption(): ListboxOption;
// @internal
protected focusAndScrollOptionIntoView(): void;
protected focusAndScrollOptionIntoView(optionToFocus?: ListboxOption | null): void;
// @internal
focusinHandler(e: FocusEvent): void;
// @internal
protected getSelectableIndex(prev: number | undefined, next: number): number;
// @internal
protected getTypeaheadMatches(): ListboxOption[];
// @internal
handleChange(source: any, propertyName: string): void;
// @internal
handleTypeAhead(key: string): void;
// @internal
protected get hasSelectableOptions(): boolean;
// @internal
keydownHandler(e: KeyboardEvent): boolean | void;
get length(): number;
// @internal
mousedownHandler(e: MouseEvent): boolean | void;
multiple: boolean;
// @internal
multipleChanged(prev: boolean | undefined, next: boolean): void;
get options(): ListboxOption[];
set options(value: ListboxOption[]);
// @internal
protected _options: ListboxOption[];
selectedIndex: number;
// @internal
selectedIndexChanged(prev: number, next: number): void;
selectedIndexChanged(prev: number | undefined, next: number): void;
selectedOptions: ListboxOption[];
// @internal
protected selectedOptionsChanged(prev: ListboxOption[] | undefined, next: ListboxOption[]): void;
Expand All @@ -1383,7 +1396,7 @@ export abstract class Listbox extends FoundationElement {
// @internal
slottedOptions: Element[];
// @internal
slottedOptionsChanged(prev: Element[] | unknown, next: Element[]): void;
slottedOptionsChanged(prev: Element[] | undefined, next: Element[]): void;
// @internal
protected static readonly TYPE_AHEAD_TIMEOUT_MS = 1000;
// @internal
Expand All @@ -1405,11 +1418,57 @@ export interface Listbox extends DelegatesARIAListbox {

// @public
export class ListboxElement extends Listbox {
// @internal
protected activeIndex: number;
// @internal
protected activeIndexChanged(prev: number | undefined, next: number): void;
// @internal
get activeOption(): ListboxOption | null;
// @internal
protected checkActiveIndex(): void;
// @internal
protected get checkedOptions(): ListboxOption[];
// @internal
protected checkFirstOption(preserveChecked?: boolean): void;
// @internal
protected checkLastOption(preserveChecked?: boolean): void;
// @internal
protected checkNextOption(preserveChecked?: boolean): void;
// @internal
protected checkPreviousOption(preserveChecked?: boolean): void;
// @internal @override
clickHandler(e: MouseEvent): boolean | void;
// @internal @override (undocumented)
connectedCallback(): void;
// @internal @override (undocumented)
disconnectedCallback(): void;
// @internal
get firstSelectedOptionIndex(): number;
// @internal @override (undocumented)
protected focusAndScrollOptionIntoView(): void;
// @internal @override
focusinHandler(e: FocusEvent): boolean | void;
// @internal
focusoutHandler(e: FocusEvent): void;
// @internal @override
keydownHandler(e: KeyboardEvent): boolean | void;
// @internal @override
mousedownHandler(e: MouseEvent): boolean | void;
// @internal @override
multipleChanged(prev: boolean | undefined, next: boolean): void;
// @internal
protected rangeStartIndex: number;
// @override
protected setSelectedOptions(): void;
size: number;
// @internal
protected sizeChanged(prev: number | unknown, next: number): void;
// @internal
toggleSelectedForAllCheckedOptions(): void;
// @internal @override (undocumented)
typeaheadBufferChanged(prev: string, next: string): void;
// @internal
protected uncheckAllOptions(preserveChecked?: boolean): void;
}

// Warning: (ae-different-release-tags) This symbol has another declaration with a different release tag
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

## Overview

The `<fast-option>` component is an option that is intended to be used with `<fast-listbox>` and `<fast-select>`.
The `<fast-option>` component is an option that is intended to be used with `<fast-listbox>`, `<fast-combobox>`, and `<fast-select>`.

**Note**: To avoid namespace collisions with the [Option() constructor](https://developer.mozilla.org/en-US/docs/Web/API/HTMLOptionElement/Option), the component class is `ListboxOption`, and our implementation is defined as `<fast-option>`. This makes the class distinct and keeps the component tag name familiar for authors.
**Note**: To avoid namespace collisions with the [`Option()` constructor](https://developer.mozilla.org/en-US/docs/Web/API/HTMLOptionElement/Option), the component class is `ListboxOption`, and our implementation is defined as `<fast-option>`. This makes the class distinct and keeps the component tag name familiar for authors.

### API

Expand Down Expand Up @@ -59,6 +59,7 @@ The `<fast-option>` component is an option that is intended to be used with `<fa

### States

- `checked` - The checked state is used when the parent `<fast-listbox>` or `<fast-select>` is in multiple selection mode. To avoid accessibility conflicts, the `checked` state should not be present in single selection mode.
- `disabled` - when disabled, user interaction has no effect. Disabling the parent `<fast-listbox>` or `<fast-select>` will also prevent user interaction on the `<fast-option>`.
- `selected` - The selected state is primarily controlled by user interactions on the parent `listbox` component. Changing the `selected` property directly will force the state to change.

Expand Down
4 changes: 2 additions & 2 deletions packages/web-components/fast-foundation/src/listbox/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ sidebar_label: listbox
custom_edit_url: https://github.com/microsoft/fast/edit/master/packages/web-components/fast-foundation/src/listbox/README.md
---

An implementation of a [listbox](https://w3c.github.io/aria-practices/#Listbox). While any DOM content is permissible as a child of the listbox, only [`fast-option`](/docs/components/listbox-option) elements, `option` elements, and slotted items with `role="option"` will be treated as options and receive keyboard support.
An implementation of a [listbox](https://www.w3.org/TR/wai-aria-practices-1.2/#Listbox). While any DOM content is permissible as a child of the listbox, only [`fast-option`](/docs/components/listbox-option) elements, `option` elements, and slotted items with `role="option"` will be treated as options and receive keyboard support.

The `listbox` component has no internals related to form association. For a form-associated `listbox`, see the [`fast-select` component](/docs/components/select).

Expand Down Expand Up @@ -59,4 +59,4 @@ See [listbox-option](/docs/components/listbox-option) for more information.

- [Component explorer examples](https://explore.fast.design/components/fast-listbox)
- [Component technical specification](https://github.com/microsoft/fast/blob/master/packages/web-components/fast-foundation/src/listbox/listbox.spec.md)
- [W3C Component Aria Practices](https://w3c.github.io/aria-practices/#Listbox)
- [W3C Component Aria Practices](https://www.w3.org/TR/wai-aria-practices-1.2/#Listbox)
Loading

0 comments on commit 45116d1

Please sign in to comment.