From 9538aa5aa606050e2a5facad991977817ec44b4e Mon Sep 17 00:00:00 2001 From: Thijs Louisse Date: Sat, 1 May 2021 18:19:36 +0200 Subject: [PATCH] chore: fix and polish custom Field tutorial --- docs/guides/how-to/create-a-custom-field.md | 188 ++++++++++++++++++++ docs/guides/index.md | 2 +- 2 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 docs/guides/how-to/create-a-custom-field.md diff --git a/docs/guides/how-to/create-a-custom-field.md b/docs/guides/how-to/create-a-custom-field.md new file mode 100644 index 000000000..4ddf5d0bc --- /dev/null +++ b/docs/guides/how-to/create-a-custom-field.md @@ -0,0 +1,188 @@ +# How To >> Create a custom field ||20 + +```js script +import { html, css, LitElement } from '@lion/core'; +import { LionField } from '@lion/form-core'; +import '@lion/form-core/define'; +import '../../docs/systems/form/assets/h-output.js'; + +// A) the custom [slot=input] or 'HTMLElementWithValue' +class DummySlider extends LitElement { + // A1) it should have a .value property of type 'string' + static properties = { value: String, reflect: true }; + + static styles = [ + css` + :host { + display: block; + padding-top: 16px; + padding-bottom: 16px; + } + + [part='rail'] { + position: relative; + display: block; + background: #eee; + height: 8px; + border-radius: 8px; + } + + [part='thumb'] { + position: absolute; + display: flex; + align-items: center; + justify-content: center; + font-size: 10px; + background: black; + color: white; + border-radius: 50%; + height: 24px; + width: 24px; + top: 50%; + transform: translateY(-50%); + transition: left 0.5s ease 0s; + } + `, + ]; + + constructor() { + super(); + this.value = '0'; + this.addEventListener('click', ev => { + this.value = `${Math.round( + ((ev.clientX - this.getClientRects()[0].x) / this.offsetWidth) * 5, + )}`; + // A2) it should have a way to tell LionField its value changed + this.dispatchEvent(new Event('dummy-slider-changed', { bubbles: true })); + }); + } + + connectedCallback() { + super.connectedCallback(); + this.setAttribute('tabindex', 0); + } + + render() { + return html`
+ ${this.value} +
`; + } +} +if (!customElements.get('dummy-slider')) { + customElements.define('dummy-slider', DummySlider); +} +``` + +Custom Fields can be created in just a few steps. All you need is an interaction element (like for instance a slider, a listbox or a combobox) and connect it to the [LionField](../../components/inputs/input/overview.md). + +> In case you want to extend a native element, follow [Extend a native Input](./extend-a-native-input.md). + +## A) an interaction element + +An interaction element (.\_inputNode) provides the means for the end user to enter a certain value, +just like native elements provide in this (think of input, textarea and select). +An example of a non native element is the [slider design pattern](https://www.w3.org/TR/wai-aria-practices-1.1/#slider) described here. + +For this tutorial, we create a dummy component 'dummy-slider' that exposes its value via +property `.value` and sends an event `dummy-slider-changed` on every value change. +To make it focusable, it has a tabindex=“0” applied. + +```js preview-story +export const createAnInteractiveElement = () => { + // A) the custom [slot=input] or 'HTMLElementWithValue' + class DummySlider extends LitElement { + // A1) it should have a .value property of type 'string' + static properties = { value: String }; + + constructor() { + super(); + this.value = 0; + this.addEventListener('click', ev => { + this.value = `${Math.round( + ((ev.clientX - this.getClientRects()[0].x) / this.offsetWidth) * 5, + )}`; + // A2) it should have a way to tell LionField its value changed + this.dispatchEvent(new Event('dummy-slider-changed', { bubbles: true })); + }); + } + + connectedCallback() { + super.connectedCallback(); + this.setAttribute('tabindex', 0); + } + + render() { + return html`
+ ${this.value} +
`; + } + } + return html``; +}; +``` + +## B) your LionField extension + +Now we want to integrate the slider in our form framework to enrich the user interface, get +validation support and all other [benefits of LionField](../../components/inputs/input/overview.md). +We start by creating a component `` that extends from `LionField`. +Then we follow the steps below: + +- **Add your interaction element** + + Here you return the element the user interacts with. By configuring it as a slot, it will end up in + light DOM, ensuring the best accessibility for the end user. + +- **Connect modelValue** + + The `user-input-changed` event is listened to by the FormatMixin: it should be regarded as the + equivalent of the `input` event of the platform, but for custom built interaction elements. + You now synchronized [modelValue](../../docs/systems/form/model-value.md), which can be regarded as + the glue to integrate all other functionality like parsing/formatting/serializing, validating, + tracking interaction states etc. + +Implement with the following code: + +```js preview-story +export const createAField = () => { + // B) your extension with all the Field goodness... + class SliderField extends LionField { + // B1) Add your interaction element as ‘input slot' + get slots() { + return { + ...super.slots, + input: () => document.createElement('dummy-slider'), + }; + } + + // B2) Connect modelValue + constructor() { + super(); + this.addEventListener('dummy-slider-changed', ev => { + ev.stopPropagation(); + this.dispatchEvent(new Event('user-input-changed')); + }); + } + + get value() { + return this._inputNode.value; + } + + set value(newV) { + this._inputNode.value = newV; + } + } + customElements.define('slider-field', SliderField); + + return html` + `; +}; +``` + +That was all! + +Now you can enhance your slider by writing custom Validators for it or by +writing a parser to get a custom modelValue type. diff --git a/docs/guides/index.md b/docs/guides/index.md index 76865b95d..86dd9cdb4 100644 --- a/docs/guides/index.md +++ b/docs/guides/index.md @@ -30,4 +30,4 @@ Especially for subclassers we have some extra documentation: Next to that we have some specific "How To" documentations. - [Get started](./how-to/get-started.md) -- [Create a custom field](./how-to/creating-a-custom-field.md) +- [Create a custom field](./how-to/create-a-custom-field.md)