spectrum-web-components
spectrum-web-components copied to clipboard
RFC: feat(picker): now leverages multiple selection
RFC: Enhancing Picker Component with Multiple Selection
Summary
This RFC proposes enhancing the sp-picker component with improved multiple selection capabilities. The changes focus on providing a better user experience by keeping the overlay open during selection and implementing proper tag-based display of selected items.
Basic Multilple Selection:
<sp-picker multiple label="Select options">
<sp-menu-item value="option1">Option 1</sp-menu-item>
<sp-menu-item value="option2">Option 2</sp-menu-item>
<sp-menu-item value="option3">Option 3</sp-menu-item>
</sp-picker>
With Icon Support
<sp-picker multiple label="Select actions">
<sp-menu-item value="edit">
<sp-icon-edit slot="icon"></sp-icon-edit>
Edit
</sp-menu-item>
<sp-menu-item value="copy">
<sp-icon-copy slot="icon"></sp-icon-copy>
Copy
</sp-menu-item>
<sp-menu-item value="delete">
<sp-icon-delete slot="icon"></sp-icon-delete>
Delete
</sp-menu-item>
</sp-picker>
Motivation
The sp-picker component currently supports single selection operation, but lacks support for selecting multiple items - a common use case in form interfaces. Users frequently need to choose several options from a dropdown list without the friction of repeated interactions.
Implementing multiple selection in the sp-picker component addresses several key user needs:
- Efficient Selection Process: Users can select multiple items in a single interaction flow, without needing to reopen the dropdown for each selection.
- Consistent Visual Representation: Selected items should appear as proper sp-tag components, providing a cohesive visual experience with the rest of the Spectrum design system.
- Rich Visual Feedback: The component should preserve visual attributes (such as icons) from the original menu items in their selected representation.
- Space Efficiency: When many items are selected, the interface should intelligently manage space constraints through techniques like showing "+n more" indicators.
Detailed Design
1. Keep Overlay Open During Multiple Selection
Modify the setValueFromItem method to check if the component is in multiple selection mode (this.multiple === true) and avoid closing the overlay when making selections:
protected async setValueFromItem(
item: MenuItem,
menuChangeEvent?: Event
): Promise<void> {
// ...existing code...
if (this.multiple) {
// Logic for multiple selection
// Do NOT close the overlay for multiple selection
return;
}
// For single selection, close the overlay as before
await this.close();
}
2. Use sp-tag Component for Selected Items
Replace the current HTML-based rendering of selected items with proper sp-tag components:
private renderSelectedTags(): TemplateResult {
if (!this.multiple || this._selectedItems.length === 0) {
return html``;
}
const visibleCount = this.maxOptionsVisible > 0
? Math.min(this._selectedItems.length, this.maxOptionsVisible)
: this._selectedItems.length;
const hiddenCount = this._selectedItems.length - visibleCount;
return html`
<div class="tags">
${this._selectedItems.slice(0, visibleCount).map((item, index) => {
if (this.renderTag) {
const customTag = this.renderTag(item, index);
if (typeof customTag === 'string') {
return html`${customTag}`;
}
return customTag;
}
// Get icon from menu item's children
const itemChildren = item.itemChildren || {};
const hasIcon = 'icon' in itemChildren && !!itemChildren.icon;
return html`
<sp-tag
size=${this.size || 'm'}
?deletable=${!this.readonly}
?disabled=${this.disabled}
?readonly=${this.readonly}
@delete=${(event: Event) => {
event.stopPropagation();
this.setValueFromItem(item);
}}
>
${hasIcon ? html`
<slot name="icon" slot="icon">${itemChildren.icon}</slot>
` : nothing}
${item.textContent}
</sp-tag>
`;
})}
${hiddenCount > 0 ? html`
<sp-tag size=${this.size || 'm'}>+${hiddenCount}</sp-tag>
` : nothing}
</div>
`;
}
3. Add Customizable Tag Rendering
Provide an API to customize the rendering of selected tags:
/**
* A function that customizes the tags to be rendered when multiple=true.
* The first argument is the option, the second is the current tag's index.
* The function should return either a Lit TemplateResult or a string containing
* trusted HTML of the symbol to render at the specified value.
*/
@property({ attribute: false })
public renderTag?: (
option: MenuItem,
index: number
) => TemplateResult | string;
4. Customize Default Max Visible Options
You can customize the default maxOptionsVisible value for a better user experience:
/**
* The maximum number of selected options to show when `multiple` is true.
* After the maximum, "+n" will be shown to indicate the number of additional items that are selected.
* Set to 0 to remove the limit.
*/
@property({ type: Number, attribute: 'max-options-visible' })
public maxOptionsVisible = 3; // Changed from 2 to 3
Adoption Strategy
This change introduces new functionality through additional API properties that don't affect existing behavior. The new multiple property and its related APIs (maxOptionsVisible and renderTag) are entirely opt-in and won't impact components using the standard single-selection behavior.
The new multiple selection API is designed with the following principles:
- Opt-in functionality: The default behavior of sp-picker remains unchanged; multiple selection is only activated when the multiple property is explicitly set to true.
- Additive API: The new API properties (multiple, maxOptionsVisible, and renderTag) extend the component's capabilities without modifying or deprecating any existing properties or methods.
Implementation Plan
- Modify the setValueFromItem method to keep the overlay open during multiple selection
- Implement tag-based rendering for selected items
- Add support for preserving icons from menu items
- Add the renderTag API for custom tag rendering
- Update CSS styles for tag layout
- Add stories demonstrating the new capabilities
- Update documentation
- Add tests for the new functionality
This implementation can be completed in a single pull request as the changes are isolated to the Picker component.