Skip to content
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

Research: Combobox / Select as webcomponents #1388

Closed
ffriedl89 opened this issue Jul 28, 2020 · 11 comments
Closed

Research: Combobox / Select as webcomponents #1388

ffriedl89 opened this issue Jul 28, 2020 · 11 comments

Comments

@ffriedl89
Copy link
Collaborator

Feature Request

Summary

We need to research how we invision the API for the select / combobox to look like in a web component world.

Feature Description

A select or combobox can potentially be the same component, but maybe we want to split them apart. This is very much undecided yet.

Compare different component libraries / design system approaches to a combobox/select component and come up with an API proposal that would work.

Some potential resources:

Keep in mind that virtualization of options is gonna be a must have. Is this something we should handle on the web component layer or is this something our consumers handle on a potential framework layer.
Test and research using one of the web components above in an Angular setup using virtual scrolling for options.
Try using react and virtualization with the web component as well.

If we decide to handle this on our own - building a template mechanism that sets the context for an option once created is something we need to do.

Some resources on how to achieve something like this might be microsoft-graph-toolkit

Outcome of this issue should be an overview of our options and pros / cons so we can make an informed decision on how to move forward.

Basic select mock

@ffriedl89 ffriedl89 changed the title Research: Combobox / Select Research: Combobox / Select as webcomponents Aug 3, 2020
@ffriedl89
Copy link
Collaborator Author

Sync with @heartdisease about API ideas for the combobox. He is improving our current experimental version for Angular

@ffriedl89 ffriedl89 added this to the Sprint 201 milestone Aug 3, 2020
@peter-affenzeller
Copy link
Contributor

peter-affenzeller commented Aug 12, 2020

I tested the microsoft-graph-toolkit TemplateHelper to generate the combobox items with a given context and template provided in a slot. This works as we hoped it would, also with nested elements in the template.
I uploaded the test to this repo

So we should be good to go to pass the list of items and (optionally) a template to the combobox and "just" add the virtualisation before actually rendering the items to only render x items, depending on the scroll position and the height of the overlay container.

Currently, the TemplateHelper.renderTemplate() method accepts an HTMLElement as root node, a template and a context (and an optional additional context). It creates all the nodes according to the given template and attaches them to the root node.
It might be better to change this behaviour and have the method only accept a template and a context and return the compiled template to then be able to output the returned HTML directly in the lit-template in the component.
What do you think?

@peter-affenzeller
Copy link
Contributor

peter-affenzeller commented Aug 13, 2020

Requirements

  • Virtual scrolling: Only part of the options should actually be rendered to avoid performance issues
  • Custom template: The user must be able to provide a template that is used for rendering the options
  • Filtering: The user must be able to type in the input to filter the list of items; filtering should happen using a filter function provided by the user
  • Scroll indicator: It must always be obvious whether there are more options --> as long as there are more options, half of the initially last visible option (or a maximum of x pixels) should be visible to clearly indicate that the container is scrollable.

Next steps requirements

  • Loading state: A loading spinner should be displayed in the overlay as long as no items are available ToDo Where should the loading spinner be displayed?
  • Multi select: The user should be able to select multiple options
  • Option groups: It should be possible to group options

Accessibility

When focused, the combobox should open when hitting the Enter, ArrowUp or ArrowDown key or when the user starts typing a filter in the input field.

When open, the user should be able to use the ArrowUp/ArrowDown key to choose from existing options. The focus for typing a filter should remain in the input field, options should not be tab-able. The selection state of the options should be indicated using the aria-selected attribute.

A selection should be submitted using the Enter key.

Pressing the Tab or Escape key should close the overlay and should not change the selected item.

When typing a filter and then tabbing out of the input field, the filter should be reset and the previously selected item should be displayed.

Custom template syntax

The user must provide the opening and closing string of template parts. This should be possible either on a global or a per-component level (has yet to be decided).

Defaults to {{ and }}.

API Proposal

WIP

The ComboBox should be split up into the main FluidComboBox component, a FluidComboBoxOptionsList component and a FluidComboBoxOption component.

FluidComboBox

Contains a FluidComboBoxOptionsList, which in turn contains FluidComboBoxOptions.
Handles opening and closing of the overlay and overlay positioning.
Accepts a template as child element that accepts base HTMLElements.

When providing a template, the opening and closing string that is used for template bindings must be set.

Inputs

Name Planned for Type Default Description
options MVP Array [] Array of options to display in the FluidComboBoxOptionsList
filterFn MVP (option: T) => boolean null Function used for filtering options
renderOptionFn MVP (option: T) => string null Function used for rendering options in the autocomplete panel
placeholder MVP string '' Placeholder to show in the input field when no option is selected
disabled MVP boolean false Boolean to determine whether the combobox is disabled
loading MVP boolean false Boolean to determine whether to show the loading indicator
selected MVP any null Item to pre-select
multiSelect v2 boolean false Boolean to determine whether more than one option can be selected simultaneously

Outputs

Name Planned for Type Description
change MVP FluidComboBoxChangeEvent Event fired when the value changes
filter-change MVP FluidComboBoxFilterChangeEvent Event fired when the user enters a value for filtering
selection-change MVP FluidComboBoxSelectionChangeEvent Event fired when the selected option changes

FluidLoadingSpinner

As a prerequisite for the FluidComboBox, we need to implement a reusable loading spinner component first.
ToDo Ask UX about L&F of the new loading spinner (or if the old design can be re-used).

FluidVirtualScroller

As a prerequisite for the FluidComboBox, we need to implement a reusable component that implements virtual scrolling
in order to be able to use the combobox with a large number of items.

See also:
https://github.com/WICG/virtual-scroller/blob/master/README.md
https://www.sitepen.com/blog/next-generation-virtual-scrolling/
https://dev.to/adamklein/build-your-own-virtual-scroll-part-i-11ib
https://blog.logrocket.com/virtual-scrolling-core-principles-and-basic-implementation-in-react/

FluidPopover

As a prerequisite for the FluidComboBox, we need to implement a reusable popover component that can be placed
perfectly beneath the input element of the combobox.

This library should be used for implementing that component:
https://popper.js.org/docs/v2/

@lukasholzer
Copy link
Contributor

lukasholzer commented Aug 14, 2020

Regarding the templates I have some concerns as it is not very flexible and extensive for the developer to have only text replacements. Think about text transformations via functions or simple conditions like an if that would not be possible but I think required.

My approach would be kinda writing functions that return an HTML string that can be put into a DocumentFragment that can then be appended.

With writing functions, you would gain the full flexibility of transforming or conditions.
To provide a simple outlet for that we could leverage something similar to JSX htm that can be rendered to a string so no compiling would be needed. It also has a very minimal footprint we are talking about ~700Bytes.

it could then be used somehow like:

<script type="text/template">
html`
  <span>${text}</span>
   {count > 0
     <span>occurrences ${count}</span>
   }
   </div>
`;
</script>
import { render } from "preact-render-to-string";

const template = document.createElement('template');
template.innerHTML = render(option); // option is the script that can be passed via slot or input

const fragment = temp.content;

alternatively, we could used tagged templates https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_templates.

This would maybe the more minimal solution.

@ffriedl89
Copy link
Collaborator Author

ffriedl89 commented Aug 14, 2020

I've done some testing and Angular, unfortunately, strips all script tags from the template. Just my 2 cents regarding declaring the script inside the template.

@lukasholzer
Copy link
Contributor

Maybe let's go with a function-first approach so that the Combobox has an Input where you can provide a function on how to render the template.

Is it possible to provide a function on LitElements as a property? Because then I would pass the rendering to the consumer where he has all the flexibilities of javascript in this function. The function has declared parameters (the input for the template) and have to return a string.

A default implementation could be done like this:

_defaultTemplate<T>(display: string, value: T): string {
   return `<option>${display}</option>`
}

@lukasholzer lukasholzer modified the milestones: Sprint 201, Sprint 202 Aug 18, 2020
@peter-affenzeller
Copy link
Contributor

As discussed in #1513, we need to properly handle the popover.

@ffriedl89
Copy link
Collaborator Author

I am not sure if this is the right place to discuss the input that is part of the combobox component - But since the input grows on focus I think it makes sense to extend the box of the input component to take the space that the largest state has.

Currently the input grows by 16px in width once focused. So I'd suggest having the label + the input moved by a padding of 8. This would mean that if multiple controls (input, select, ...) are in a vertical form layout, that all labels of any controls need to be aligned. Maybe a form control makes sense that takes care of unifying the spacing for labels + inputs.

@heartdisease
Copy link
Contributor

heartdisease commented Aug 31, 2020

I would like to amend the proposal by adding a new input for rendering the display string for the currently selection option:

Inputs

Name Planned for Type Default Description
displayNameFn MVP (option: T) => string (option) => `${option}` Function that returns a display string (no HTML allowed) for a given option.

This would also provide an easy solution for this issue: #1527

@myieye
Copy link
Contributor

myieye commented Sep 22, 2020

Something small that I think is important: The current DtCombobox, uses an option's value as the default "selected option" display text. I think it makes a lot more sense to use the option's textContent.

  1. The textContent is always a UI-friendly value (value is only sometimes a UI friendly value)
  2. Using textContent is what standard select's do

@lukasholzer lukasholzer linked a pull request Sep 23, 2020 that will close this issue
4 tasks
@lukasholzer
Copy link
Contributor

We will close this issue as web components are not the way we want to create components in the future this is related to a shift of priorities in the team.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants