From f7f7f6e8c990543898bed3159dffcd6048485eeb Mon Sep 17 00:00:00 2001 From: Kevin Schaaf Date: Mon, 28 Aug 2023 11:24:39 -0700 Subject: [PATCH 1/8] [slottable-request] Initial draft --- README.md | 2 + proposals/slottable-request.md | 86 ++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 proposals/slottable-request.md diff --git a/README.md b/README.md index 7242d13..6ba1c6f 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,11 @@ Check out the [Issues](https://github.com/webcomponents/community-protocols/issu |----------------|------------------|--------| | [Context] | Benjamin Delarre | Draft | | [Pending Task] | Justin Fagnani | Draft | +| [Slottable Request] | Kevin Schaaf | Draft | [Context]: https://github.com/webcomponents/community-protocols/blob/main/proposals/context.md [Pending Task]: https://github.com/webcomponents/community-protocols/blob/main/proposals/pending-task.md +[Slottable Request]: https://github.com/webcomponents/community-protocols/blob/main/proposals/slottable-request.md ## Status diff --git a/proposals/slottable-request.md b/proposals/slottable-request.md new file mode 100644 index 0000000..6fe779e --- /dev/null +++ b/proposals/slottable-request.md @@ -0,0 +1,86 @@ +# Slottable Request Protocol +aka "Render Props for Web Components" + +**Author**: Kevin Schaaf (@kevinpschaaf, Google) + +**Status**: Draft + +**Last Updated**: 2023-08-28 + + +# Background + +There are often use cases that require a component to render UI based on data, but where the specific rendering of the data should be controllable by the user. Use cases of this include: + +* A data table or tree component, which manages the rendering of cells or tree nodes, but where the given look and feel of the items is customizable by the user. +* A virtual list component, which optimizes rendering a list of data by only rendering a subset of user-templated items based on which array instances are visible on the screen. +* Components that fetch data themselves, but provide the ability for the user to customize the rendering of the data. + +This proposal describes an interoperable protocol for components to request that their owning context render slotted content into themselves, using data provided via the protocol. + +# Goals + +* The ability for users of a component to render requested content using a templating system of their choice, using data supplied by the component. +* The ability for requested content to be logically composed by the requesting component into the component's shadow root at a position of its choice. +* The ability for requested content to be styled in the same "user" scope the component in question was created in, and using styling mechanisms of the user's choice. +* The ability to implement the protocol inside framework-specific wrappers or helpers that expose a user API that looks similar or identical to framework-specific patterns (i.e. "render props" for React).. + + +# Design / Proposal + + +## Protocol Definition + +Concretely, the protocol involves dispatching a well-known DOM event to request slotted content (aka "[slottables](https://dom.spec.whatwg.org/#light-tree-slotables)", per the DOM spec) be rendered with the provided `data` argument and assigned to the given `slotName`: + +```ts +export class SlotableRequestEvent extends Event { + readonly data: T | typeof remove; + readonly name: Name; + readonly slotName: string; + constructor(name: Name, data: unknown, key?: string) { + super('slottable-request', {bubbles: false, composed: false}); + this.name = name; + this.data = data; + this.slotName = key !== undefined ? `${name}.${key}` : name; + } +} + +export const remove = Symbol('slottable-request-remove'); +``` + +When a component wants to render customizable content, it (1) fires a `slottable-request` event for a given slot name, and (2) renders a `<slot>` element into its shadow root corresponding to the requested slot name. + +Each time the `slottable-request` event is received, the user should use the provided `data` to render (or update) slotted content into the component that fired the event, where each top-level child rendered for a given request is assigned to the provided `slotName`. The `name` field on the event is used to determine what type of content to render; each unique `name` will generally map to a specific user-managed template. + +Because components may request multiple slotted instances for the same conceptual `name` (for example, in the case of a 'list-item` slot request), the `slotName` may differ from the `name`. Thus, `name` should be used to select which template to render, and `slotName` should be assigned to the given rendered _instance_ and used as a unique key to identify which instance to update for subsequent requests. + +When requesting a specific slot instance, the event accepts an optional 3rd `key` argument; this is used to generate a unique `slotName` by suffixing the `name` with `key`. + +The `remove` symbol is provided as a sentinel value for `data` to indicate that the given content assigned to `slotName` should be removed, and should be fired when the associated `<slot>` is no longer rendered to avoid leaking light-DOM children. + + +## Examples + +* ["Travel Calendar"](https://lit.dev/playground/#gist=205ee0ccc0ea4d0420608808942d2655) customization example (raw with no helpers) +* [Lit proof-of-concept](https://lit.dev/playground/#gist=2974fec927ef67b30d82a6ff7d05740a). Includes demos of: + * Raw implementation of handling the `slottable-request` event using Lit (see [this description](of possible API for Lit-specific helpers)) + * Implementing the protocol using a Lit directive + * Implementing the protocol in a React web component wrapper + + +## Comparison with Render Props + +Simply modeling the need expressed above as a "render prop", aka a property on the component that accepts a function that receives data and produces/updates DOM based on that data, introduces a number of complications making it ill-suited for interoperable web components: + +1. **Different rendering libraries may be used between the caller and the author.** When both the usage site and the component itself are implemented using the same rendering library, render props are as simple as providing a framework-specific template reference (often a function) to the component that the component can natively render. However, Web Components introduce the capability to implement the "inside" of a component with a different rendering library than the outside library rendering the component (if any). Requiring a render prop to be provided using the same rendering library the component was authored with leaks implementation details and has negative DX implications, forcing the user to context-switch into a different templating language they may not otherwise be familiar with. +2. **Rendering a user-provided template to Light DOM is risky.** The light DOM children of a component are logically owned by the calling scope (the "user" of the component, not the component itself). Rendering children outside of the shadow root may conflict with user-scope rendering libraries by violating assumptions it makes for reconciling DOM (e.g., incremental DOM diffs against live DOM and may remove children it did not previously render). +3. **Rendering a user-provided template to Shadow DOM is problematic for styling.** If the component used a render prop to render DOM into its shadow root, that DOM would be subject to the component's shadow styling, rather than the styling of the user's scope. Providing a mechanism to render user styles into the shadow root along with the protocol would likely feel foreign to the caller. + +Defining a "framework-agnostic render prop" protocol was explored in but rejected due to these complications. Instead, this proposal defines an event-based protocol to request the outside scope render and provide slotted children, side-stepping virtually all of the issues raised there. + +## API alternatives: + +* The "remove" case is handled as a sentinel using the same event. We could have a separate `remove-slottable-request` event, but that seemed a bit annoying to listen for two events. +* Explicitly treating `key` as a first-class thing is a choice; it could be up to the user to generate and request unique slot names, but then it would become another protocol/parsing exercise for the receiver to know that e.g. `item.4` and `item.42` should use the `item` template but `header.3` should use the header template. That seems annoying, so I landed on the requestor providing the `name` and `key` and the user getting a pre-baked `slotName` to use for it, but they still need the un-concatenated `name` to select the template. +* Rather than sending individual `slottable-request` events for each name/key instance, we could define a more complex payload for the event to describe all of the instance data & slots required. That does not lend itself as nicely to helpers to allow rendering different named slots into different parts of the template, since all of those requests would need to be batched by one orchestrator. It's certainly possible to build such a thing, with the batching keyed off of e.g. `part.options.host`, but didn't want to go directly there first. Also, each slottable-request conceptually mapping to a render-prop call seems good for keeping the concepts aligned. We clearly need to perf-test this, however, and that might push us to a single-event model. From 8e1ee6b5eef0f0c50978ca24beb2454f64924cee Mon Sep 17 00:00:00 2001 From: Kevin Schaaf Date: Mon, 28 Aug 2023 13:25:07 -0700 Subject: [PATCH 2/8] Update proposals/slottable-request.md Co-authored-by: Dave Rupert --- proposals/slottable-request.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/slottable-request.md b/proposals/slottable-request.md index 6fe779e..e8526d7 100644 --- a/proposals/slottable-request.md +++ b/proposals/slottable-request.md @@ -53,7 +53,7 @@ When a component wants to render customizable content, it (1) fires a `slottable Each time the `slottable-request` event is received, the user should use the provided `data` to render (or update) slotted content into the component that fired the event, where each top-level child rendered for a given request is assigned to the provided `slotName`. The `name` field on the event is used to determine what type of content to render; each unique `name` will generally map to a specific user-managed template. -Because components may request multiple slotted instances for the same conceptual `name` (for example, in the case of a 'list-item` slot request), the `slotName` may differ from the `name`. Thus, `name` should be used to select which template to render, and `slotName` should be assigned to the given rendered _instance_ and used as a unique key to identify which instance to update for subsequent requests. +Because components may request multiple slotted instances for the same conceptual `name` (for example, in the case of a `list-item` slot request), the `slotName` may differ from the `name`. Thus, `name` should be used to select which template to render, and `slotName` should be assigned to the given rendered _instance_ and used as a unique key to identify which instance to update for subsequent requests. When requesting a specific slot instance, the event accepts an optional 3rd `key` argument; this is used to generate a unique `slotName` by suffixing the `name` with `key`. From b0397fd2033f162a36e7d6e466d538a396f7911d Mon Sep 17 00:00:00 2001 From: Kevin Schaaf Date: Mon, 28 Aug 2023 13:40:29 -0700 Subject: [PATCH 3/8] Fix typos --- proposals/slottable-request.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proposals/slottable-request.md b/proposals/slottable-request.md index e8526d7..69402c6 100644 --- a/proposals/slottable-request.md +++ b/proposals/slottable-request.md @@ -23,7 +23,7 @@ This proposal describes an interoperable protocol for components to request that * The ability for users of a component to render requested content using a templating system of their choice, using data supplied by the component. * The ability for requested content to be logically composed by the requesting component into the component's shadow root at a position of its choice. * The ability for requested content to be styled in the same "user" scope the component in question was created in, and using styling mechanisms of the user's choice. -* The ability to implement the protocol inside framework-specific wrappers or helpers that expose a user API that looks similar or identical to framework-specific patterns (i.e. "render props" for React).. +* The ability to implement the protocol inside framework-specific wrappers or helpers that expose a user API that looks similar or identical to framework-specific patterns (i.e. "render props" for React). # Design / Proposal @@ -64,7 +64,7 @@ The `remove` symbol is provided as a sentinel value for `data` to indicate that * ["Travel Calendar"](https://lit.dev/playground/#gist=205ee0ccc0ea4d0420608808942d2655) customization example (raw with no helpers) * [Lit proof-of-concept](https://lit.dev/playground/#gist=2974fec927ef67b30d82a6ff7d05740a). Includes demos of: - * Raw implementation of handling the `slottable-request` event using Lit (see [this description](of possible API for Lit-specific helpers)) + * Raw implementation of handling the `slottable-request` event using Lit (see [this description](https://gist.github.com/kevinpschaaf/0fe117368411f340aa3019dceeaa465e) of possible API for Lit-specific helpers) * Implementing the protocol using a Lit directive * Implementing the protocol in a React web component wrapper From 47bbb7eaf9ad216d8d246f840448d0b3f2110844 Mon Sep 17 00:00:00 2001 From: Kevin Schaaf Date: Mon, 28 Aug 2023 13:42:03 -0700 Subject: [PATCH 4/8] Fix more markdown conversion mistakes --- proposals/slottable-request.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proposals/slottable-request.md b/proposals/slottable-request.md index 69402c6..6546800 100644 --- a/proposals/slottable-request.md +++ b/proposals/slottable-request.md @@ -49,7 +49,7 @@ export class SlotableRequestEvent extends Even export const remove = Symbol('slottable-request-remove'); ``` -When a component wants to render customizable content, it (1) fires a `slottable-request` event for a given slot name, and (2) renders a `<slot>` element into its shadow root corresponding to the requested slot name. +When a component wants to render customizable content, it (1) fires a `slottable-request` event for a given slot name, and (2) renders a `` element into its shadow root corresponding to the requested slot name. Each time the `slottable-request` event is received, the user should use the provided `data` to render (or update) slotted content into the component that fired the event, where each top-level child rendered for a given request is assigned to the provided `slotName`. The `name` field on the event is used to determine what type of content to render; each unique `name` will generally map to a specific user-managed template. @@ -57,7 +57,7 @@ Because components may request multiple slotted instances for the same conceptua When requesting a specific slot instance, the event accepts an optional 3rd `key` argument; this is used to generate a unique `slotName` by suffixing the `name` with `key`. -The `remove` symbol is provided as a sentinel value for `data` to indicate that the given content assigned to `slotName` should be removed, and should be fired when the associated `<slot>` is no longer rendered to avoid leaking light-DOM children. +The `remove` symbol is provided as a sentinel value for `data` to indicate that the given content assigned to `slotName` should be removed, and should be fired when the associated `` is no longer rendered to avoid leaking light-DOM children. ## Examples From d3d1a2fe8e86b11852124705344e8742f8a145fc Mon Sep 17 00:00:00 2001 From: Kevin Schaaf Date: Mon, 28 Aug 2023 13:43:02 -0700 Subject: [PATCH 5/8] Update slottable-request.md --- proposals/slottable-request.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proposals/slottable-request.md b/proposals/slottable-request.md index 6546800..0da8e48 100644 --- a/proposals/slottable-request.md +++ b/proposals/slottable-request.md @@ -64,8 +64,8 @@ The `remove` symbol is provided as a sentinel value for `data` to indicate that * ["Travel Calendar"](https://lit.dev/playground/#gist=205ee0ccc0ea4d0420608808942d2655) customization example (raw with no helpers) * [Lit proof-of-concept](https://lit.dev/playground/#gist=2974fec927ef67b30d82a6ff7d05740a). Includes demos of: - * Raw implementation of handling the `slottable-request` event using Lit (see [this description](https://gist.github.com/kevinpschaaf/0fe117368411f340aa3019dceeaa465e) of possible API for Lit-specific helpers) - * Implementing the protocol using a Lit directive + * Raw implementation of handling the `slottable-request` event using Lit + * Implementing the protocol using a Lit directive (see [this description](https://gist.github.com/kevinpschaaf/0fe117368411f340aa3019dceeaa465e) of possible API for Lit-specific helpers) * Implementing the protocol in a React web component wrapper From e060b07efabb4a63ad7be0ee5916918af5b79695 Mon Sep 17 00:00:00 2001 From: Kevin Schaaf Date: Wed, 8 Nov 2023 10:46:42 -0800 Subject: [PATCH 6/8] A few updates based on review comments --- proposals/slottable-request.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/proposals/slottable-request.md b/proposals/slottable-request.md index 0da8e48..8defdcb 100644 --- a/proposals/slottable-request.md +++ b/proposals/slottable-request.md @@ -10,21 +10,28 @@ aka "Render Props for Web Components" # Background -There are often use cases that require a component to render UI based on data, but where the specific rendering of the data should be controllable by the user. Use cases of this include: +There are often use cases that require a component to render UI on-demand and/or based on data, but where the specific rendering of the data should be controllable by the user. Use cases of this include: * A data table or tree component, which manages the rendering of cells or tree nodes, but where the given look and feel of the items is customizable by the user. * A virtual list component, which optimizes rendering a list of data by only rendering a subset of user-templated items based on which array instances are visible on the screen. +* A tooltip component whose user-provided slotted content will be rendered rarely and on-demand based on user interactions handled by the tooltip. * Components that fetch data themselves, but provide the ability for the user to customize the rendering of the data. This proposal describes an interoperable protocol for components to request that their owning context render slotted content into themselves, using data provided via the protocol. # Goals -* The ability for users of a component to render requested content using a templating system of their choice, using data supplied by the component. -* The ability for requested content to be logically composed by the requesting component into the component's shadow root at a position of its choice. +The high-level goal is to introduce a protocol to allow a component to request that the owner provide content to be rendered on-demand, and optionally based on data that is provided by the component. + +Specific goals within this include: + +* The ability for users of a component to render requested content using a templating system of their choice. +* The ability for users of a component to render requested content using data supplied by the component. +* The ability for requested content to be rendered into the requesting component's shadow root at a position of its choice. * The ability for requested content to be styled in the same "user" scope the component in question was created in, and using styling mechanisms of the user's choice. +* The ability to use a component that requests content via the protocol in plain JS without requiring a framework. * The ability to implement the protocol inside framework-specific wrappers or helpers that expose a user API that looks similar or identical to framework-specific patterns (i.e. "render props" for React). - +* The ability to provide slotted content lazily for better performance, since the component may defer rendering slotted content based on the state of the component. # Design / Proposal @@ -34,7 +41,7 @@ This proposal describes an interoperable protocol for components to request that Concretely, the protocol involves dispatching a well-known DOM event to request slotted content (aka "[slottables](https://dom.spec.whatwg.org/#light-tree-slotables)", per the DOM spec) be rendered with the provided `data` argument and assigned to the given `slotName`: ```ts -export class SlotableRequestEvent extends Event { +export class SlottableRequestEvent extends Event { readonly data: T | typeof remove; readonly name: Name; readonly slotName: string; From 12df8ee1a85d8875b7b397cc8d85dd389285a11b Mon Sep 17 00:00:00 2001 From: Kevin Schaaf Date: Wed, 8 Nov 2023 10:47:18 -0800 Subject: [PATCH 7/8] Update last updated date --- proposals/slottable-request.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/slottable-request.md b/proposals/slottable-request.md index 8defdcb..32a325f 100644 --- a/proposals/slottable-request.md +++ b/proposals/slottable-request.md @@ -5,7 +5,7 @@ aka "Render Props for Web Components" **Status**: Draft -**Last Updated**: 2023-08-28 +**Last Updated**: 2023-11-08 # Background From 85530e8e66a427b90e1ed9494218b79b36a3bce9 Mon Sep 17 00:00:00 2001 From: Westbrook Johnson Date: Thu, 4 Apr 2024 11:30:05 -0400 Subject: [PATCH 8/8] Reduce status level --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a315396..f9bdaec 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Check out the [Issues](https://github.com/webcomponents/community-protocols/issu |----------------|------------------|------------| | [Context] | Benjamin Delarre | Candidate | | [Pending Task] | Justin Fagnani | Draft | -| [Slottable Request] | Kevin Schaaf | Draft | +| [Slottable Request] | Kevin Schaaf | Proposal | [Context]: https://github.com/webcomponents/community-protocols/blob/main/proposals/context.md [Pending Task]: https://github.com/webcomponents/community-protocols/blob/main/proposals/pending-task.md