microsoft / fast

The adaptive interface system for modern web experiences.
https://www.fast.design
Other
9.23k stars 589 forks source link

feat: make repeat directive optionally accept a key #6919

Open sknoslo opened 6 months ago

sknoslo commented 6 months ago

πŸ™‹ Feature Request

Add an optional key to the repeat directive, so that repeat can map a DOM node to a specific item when updating views. Similar to how React or Lit work. This would be beneficial for a couple of reasons:

  1. DOM nodes used within a repeat can maintain their own internal state.
  2. Assistive technology, such as a screen reader, will be able to better understand when content has changed.
  3. Potentially less expensive for some operations?

πŸ€” Expected Behavior

When an array is sorted, or items are added or removed, existing DOM nodes should only be reused for an item with the same key. New DOM nodes should be created for keys that did not previously exist, and DOM nodes should be removed for keys that no longer exist. DOM nodes should never be reused for items with different keys.

😯 Current Behavior

When an array is sorted, or items are added or removed, existing DOM nodes are reused just based on position, and the nodes do not map 1-to-1 with the array elements.

πŸ’ Possible Solution

Add a optional key function to the repeat directive. Maintain a map of keys -> DOM nodes, and make updates accordingly. Might look something like:

repeat(
  x => x.items,
  html`<div>${item.uniqueId}</div>`,
  { keyFn: (item) => item.uniqueId }
)

Adding a separate "keyed repeat" directive could also be a good option if it makes more sense to keep the behavior distinct.

πŸ”¦ Context

I was building a notification component that could stack alerts to display for a short time. These elements had role="alert" so that a screen reader would announce them when they showed up. But, because nodes are recycled, as new alerts show up or were dismissed, the contents of existing nodes change and the screen reader announces them again, as if they are actually new.

I had to fall back to manually managing the DOM instead of using a template with the repeat directive.

πŸ’» Examples

Comparison of behavior between Lit and FAST on StackBlitz. This is a contrived example where a parent element renders a list of numbers in a child element, and the color of the child element can be toggled. This toggle state is owned by each child element. If you toggle the first (or last) item in the list, and then reverse the list, you'll see how the behavior differs.

imink commented 3 months ago

More info, If you change the repeatable component to be pure component (do not own its own state), it works. https://stackblitz.com/edit/vitejs-vite-prtfgs?file=src%2Ffast-repeater.ts

imink commented 2 weeks ago

To simplify, we need repeat directive maintainning state with Key, just like vuejs or lit. https://vuejs.org/guide/essentials/list.html#maintaining-state-with-key https://github.com/lit/lit/blob/ff87eb461dfbfa8fd0101a6a9067dcaaa9b49f92/packages/lit-html/src/directives/repeat.ts#L114

This is vital for cases loopping complex components that has its own state, i.e. treeview component with virtualization.