Skip to content

RFC: feat(picker): now leverages multiple selection #5383

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

Rajdeepc
Copy link
Contributor

@Rajdeepc Rajdeepc commented Apr 19, 2025

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:

  1. Efficient Selection Process: Users can select multiple items in a single interaction flow, without needing to reopen the dropdown for each selection.
  2. 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.
  3. Rich Visual Feedback: The component should preserve visual attributes (such as icons) from the original menu items in their selected representation.
  4. 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:

  1. 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.
  2. 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

  1. Modify the setValueFromItem method to keep the overlay open during multiple selection
  2. Implement tag-based rendering for selected items
  3. Add support for preserving icons from menu items
  4. Add the renderTag API for custom tag rendering
  5. Update CSS styles for tag layout
  6. Add stories demonstrating the new capabilities
  7. Update documentation
  8. 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.

@Rajdeepc Rajdeepc self-assigned this Apr 19, 2025
Copy link

changeset-bot bot commented Apr 19, 2025

⚠️ No Changeset found

Latest commit: e7d65ca

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@Rajdeepc Rajdeepc changed the title feat(picker): now leverages multiple selection [ DO NOT MERGE] feat(picker): now leverages multiple selection Apr 19, 2025
Copy link

Branch preview

Review the following VRT differences

When a visual regression test fails (or has previously failed while working on this branch), its results can be found in the following URLs:

If the changes are expected, update the current_golden_images_cache hash in the circleci config to accept the new images. Instructions are included in that file.
If the changes are unexpected, you can investigate the cause of the differences and update the code accordingly.

Copy link

Tachometer results

Currently, no packages are changed by this PR...

@Rajdeepc Rajdeepc changed the title [ DO NOT MERGE] feat(picker): now leverages multiple selection RFC: feat(picker): now leverages multiple selection Apr 20, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant