diff --git a/.changeset/warm-forks-press.md b/.changeset/warm-forks-press.md index 1f1972650..bcee6b721 100644 --- a/.changeset/warm-forks-press.md +++ b/.changeset/warm-forks-press.md @@ -2,4 +2,5 @@ '@lion/ui': patch --- -Align with SlotMixin rerender functionality + fix interaction state synchronization +[input-tel] Align with SlotMixin rerender functionality +[input-tel-dropdown] Align with SlotMixin rerender functionality + fix interaction state synchronization diff --git a/docs/fundamentals/rationales/_assets/theoryOfFormsLion.pdf b/docs/fundamentals/rationales/_assets/theoryOfFormsLion.pdf new file mode 100644 index 000000000..7e09daad0 Binary files /dev/null and b/docs/fundamentals/rationales/_assets/theoryOfFormsLion.pdf differ diff --git a/docs/fundamentals/rationales/accessibility.md b/docs/fundamentals/rationales/accessibility.md new file mode 100644 index 000000000..92477d6cc --- /dev/null +++ b/docs/fundamentals/rationales/accessibility.md @@ -0,0 +1,28 @@ +# Rationales >> Accessibility ||5 + +In Lion accessibility is a first class citizen. That means accessibility is never an afterthought. + +Whenever we create a component, we do some thorough research first. Experience from past web component libraries learned us that when accessibility was dealt with in hindsight, a lot of concessions needed to be made. It made us want to go back to the drawing board. Lion was started with this 'fresh drawing board'. +In general, we try to leverage out of the box accessible platform solutions as much as possible. When no platform solutions are available, we try to map our components to [aria patterns and widgets as defined by W3C](https://www.w3.org/WAI/ARIA/apg/) where possible. +Building accessible components takes a lot of time, practice and knowledge about screen reader and browser implementations. Then, it takes many testing iterations to finetune a component. + +More on this topic can be found in the online panel discussion [Web Components, Design Systems and Accessibility](https://www.youtube.com/watch?v=xz8yRVJMP2k&t=1190s). The discussion also contains a [dedicated section about accessible form components](https://www.youtube.com/watch?v=xz8yRVJMP2k&t=1917s) + +## Shadow roots and accessibility + +Since our components and applications consist of multiple shadow roots that need to be able to reference each other, designing accessible components takes extra strategy and planning. +A practical example: all form components inside a form need to be able to lay accessible relations for presenting feedback messages via the screen reader. For this, they need consistent designs so that all form components are interoperable. So, light dom needs to be leveraged until the [AOM specification](https://wicg.github.io/aom/explainer.html) is implemented. + +## Some details about the form system + +A huge part of Lion consists of its form system and components. + +This [presentation about accessible form components](../_assets/theoryOfFormsLion.pdf) explains how accesibility is built-in in all form components. + +## SlotMixin + +Internally, we delegate all intricacies involved in managing light dom to SlotMixin. +SlotMixin automatically mediates between light dom provided by the user ('public slots') and light dom provided by the component author ('private slots'). +Also, SlotMixin allows to hook into the reactive update loop of LitElement (automatically rerendering on property changes) and it automatically respects +the scoped registry belonging to the shadow root. +More details about SlotMixin can be found in the [SlotMixin documentation](../systems/core/SlotMixin.md) diff --git a/docs/fundamentals/systems/core/SlotMixin.md b/docs/fundamentals/systems/core/SlotMixin.md new file mode 100644 index 000000000..a70205b87 --- /dev/null +++ b/docs/fundamentals/systems/core/SlotMixin.md @@ -0,0 +1,136 @@ +# Systems >> Core >> SlotMixin ||20 + +The SlotMixin is made for solving accessibility challenges that inherently come with the usage of shadow dom. +Until [AOM](https://wicg.github.io/aom/explainer.html) is in place, it is not possible to create relations between different shadow doms. +The need for this can occur in the following situations: + +1. A user defined slot. For instance: + +```html + +
Input description
+
+``` + +The help text here needs to be connected to the input element that may live in shadow dom. The input needs to have `aria-describedby="help-text-id".` + +2. An interplay of multiple nested web components. For instance: + +```html + + + +
Group errror message
+
+``` + +In the case above, all inputs need to be able to refer the error message of their parent. + +In a nutshell: SlotMixin helps you with everything related to rendering light dom (i.e. rendering to slots). +So that you can build accessible ui components with ease, while delegating all edge cases to SlotMixin. +Edge cases that it solves: + +- rendering light dom in context of scoped customElementRegistries: we respect the customElementRegistry bound to your ShadowRoot +- the concept of rerendering based on property effects +- easily render lit templates + +So, what does the api look like? SlotMixin can be used like this: + +```js +class AccessibleControl extends SlotMixin(LitElement) { + get slots() { + return { + ...super.slots, + 'public-element-slot': () => document.createElement('input'), + '_private-template-slot': () => html``, + }; + } +} +``` + +## SlotFunctionResults + +The `SlotFunctionResult` is the output of the functions provided in `get slots()`. It can output the four types: + +```ts +Element | TemplateResult | SlotRerenderObject | undefined; +``` + +Below configuration gives an example of all of them, after which we explain when each type should be used + +```js +class AccessibleControl extends SlotMixin(LitElement) { + get slots() { + return { + ...super.slots, + // Element + 'public-element-slot': () => document.createElement('input'), + // TemplateResult + '_private-template-slot': () => html``, + // SlotRerenderObject + 'rerenderable-slot': () => { + return { + template: html`${this.litProperty}`, + afterRender: () => { /** sync some state */ }, + } + }, + // undefined (conditional slot) + '' => () => { + if (conditionApplies) { + return html`
default slot
`; + } + return undefined; + }, + }; + } +} +``` + +### Element + +For simple cases, an element can be returned. Use this when no web component is needed. + +### TemplateResult + +Return a TemplateResult when you need web components in your light dom. They will be automatically scoped correctly (to the scoped registry belonging to your shadowRoot) +If your template needs to rerender as well, use a `SlotRerenderObject`. + +### SlotRerenderObject + +A `SlotRerenderObject` looks like this: + +```ts +{ + template: TemplateResult; + afterRender?: Function; +}; +``` + +It is meant for complex templates that need rerenders. Normally - when rendering into shadow dom via `LitElement.render` - we get rerenders +"for free" via [property effects](https://lit.dev/docs/components/properties/#when-properties-change). +When we configure `SlotFunctionResult` to return a `SlotRerenderObject`, we get the same behavior for light dom. +For this rerendering to work predictably (no focus and other interaction issues), the slot will be created with a wrapper div. + +## Private and public slots + +Some elements provide a property/attribute api with a fallback to content projection as a means to provide more advanced html. +For instance, a simple text label is provided like this: + +```html + +``` + +- A more advanced label (using html that can't be provided via a string) can be provided like this: + +```html + + + +``` + +- In the property/attribute case, SlotMixin adds the `