Lion Logo Lion Guides Components Docs Blog Toggle darkmode

How To: Create a custom field

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.

In case you want to extend a native element, follow Extend a native Input.

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 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.

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` <div part="rail">
        <span part="thumb" style="left:___HTML_0___%;">___HTML_1___</span>
      </div>`;
    }
  }
  return html`<dummy-slider></dummy-slider>`;
};

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. We start by creating a component <slider-field> 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, 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:

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`<slider-field
      label="SliderField"
      help-text="Press to see how modelValue is synchronized"
    ></slider-field>
    <h-output .show="${['modelValue', 'touched', 'dirty', 'focused']}"></h-output>`;
};

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.