npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@wincc-oa/wui-forms

v1.2.2

Published

WinCC Open Architecture Dashboard project.

Downloads

113

Readme

WinCCOA WebComponent Dashboard

This package is part of the workspace for the WinCC Open Architecture WebComponent Dashboard, built using Lit and managed with Nx.

Usage information and reference details can be found in the WinCC OA documentation.

Content

This library consists of core elements:

wui-json-form component

This is the component responsible for generating a form based on the provided information. The simpliest way to use it could be like that:

<script>
  const data = {
    name: 'Joe',
    lastname: 'Doe',
    age: 10
  };
</script>
<wui-json-form .initData="${data}"></wui-json-form>

This will generate a form consisting of 3 inputs and a submit button.

wui-json-form accepts following properties:

  • initData - initial data to populate the form. In case there is no schema provided the form will be generated based on this object,
  • schema - JSON Schema containing all the entries of the form,
  • uiSchema - JSON UI Schema containing the details about how to generate the form (order of elements, labels, types of elements),
  • renderers - list of available functions to render form inputs or sections.

renderers

Functions rendering the JSON Schema registry entry.

Example:

export const inputRenderer = ({ path, element }: RendererConfig) => {
    <ix-field-label for=${path}>${element.label}</ix-field-label>
    <input type=${element.options.format} id=${path} name=${path}>
};

testers

Functions used to determine which renderer should be used for which schema entry.

Example:

export const uiScopeIncludes = (text: string) => (uischema: any) => uischema.scope?.includes(text);

wui-input-error component

Custom element that displays an error message for the closest input element. It also adds error class to the parent element.

<div>
  <label for="firstname" class="typography-label">First name</label>
  <input id="firstname" name="firstname" minlength="5" />
  <wui-input-error></wui-input-error>
</div>

Custom validation

The wui-json-form component accepts the additionalErrors object. It can contain custom errors for each form property.

Example:

interface Address {
  street: string;
  city: string;
}

const formSchema = {
  type: 'object',
  properties: {
    name: { type: 'string' },
    address: { type: 'object', properties: { street: { type: 'string'}, city: { type: 'string' }}
  },
};

const additionalErrors: AdditionalErrors = {
  name: {
    validator: (name: string) => name.length < 3 ? 'name is too short' : ''
  }
  address: {
    validator: (address: Address): Record<keyof Address, string> => ({
      street: address.street === 'street' ? 'incorrect street name' : ''
      city: address.city === 'Cracow' ? '' : 'Error'
    })
  }
}

You can also rely on external libraries like ajv.

Example:

import Ajv from 'ajv';

const formSchema = {
  type: 'object',
  properties: {
    range: {
      type: 'object',
      properties: {
        min: { type: 'number', minimum: 20 },
        max: { type: 'number', maximum: 50 }
      },
      required: ['min', 'max']
    }
  },
  required: ['range']
};

const ajv = new Ajv({ allErrors: true });
const validate = ajv.compile(customValidationFormSchema);

const additionalErrors: AdditionalErrors = {
  range: {
    validator: (data: RangeValue): Record<keyof RangeValue, string> => {
      validate({ range: data });
      const errors = validate.errors;

      return {
        min: errors?.find(({ instancePath, params }) => instancePath.includes('range/min') || (instancePath.includes('range') && params?.['missingProperty'] === 'min'))?.message || '',
        max: errors?.find(({ instancePath, params }) => instancePath.includes('range/max') || (instancePath.includes('range') && params?.['missingProperty'] === 'max'))?.message || ''
      };
    }
  }
};

For new renderers custom validation has to be implemented if needed. To use additionalErrors within your component you have to consume the JsonFormContext.

Example:

@customElement('wui-custom-renderer')
export class WuiCustomRenderer extends LitElement {
  @consume({ context: jsonFormContext })
  context!: JsonFormContext;
  @property({ type: String, reflect: true }) name = '';

  render() {
    const customValidators = this.context?.additionalErrors?.[this.name];

    if (customValidators.validator) {
      const errors = customValidators.validator(value);
    }

    ...

    return html`...`
  }
}

Setting up form rules in a schema

You can find extended documentation here: https://jsonforms.io/docs/uischema/rules/

Rules allow for dynamic aspects for a form, e.g. by hiding or disabling UI schema elements.

A rule may be attached to any UI schema element and can be defined with the ==rule== property which looks like:

"rule": {
  "effect": "HIDE" | "SHOW" | "ENABLE" | "DISABLE",
  "condition": {
    "scope": "<UI Schema scope>",
    "schema": JSON Schema
  }
}

A rule basically works by first evaluating the ==condition== property and in case it evaluates to true, executing the associated ==effect==.

Rule Condition

The rule ==condition== object contains a ==scope== and a ==schema== property. The ==schema== property is a standard JSON schema object. This means, everything that can be specified using JSON schema can be used in the rule condition. The ==schema== is validated against the data specified in the ==scope== property. If the ==scope== data matches the ==schema== the rule evaluates to true and the rule effect is applied.

Note, ==SchemaBasedConditions== have been introduced with version 2.0.6 and have become the new default. The previous format via ==type== and ==expectedValue== properties is still supported for the time being.

Examples

Below are some common rule examples.

To match a scope variable to a specific value, "const" can be used:

"rule": {
  "effect": "HIDE",
  "condition": {
    "scope": "#/properties/counter",
    "schema": { const: 10 }
  }
}

Here, the control is hidden when the counter property is equal to 10.

Similar, to match multiple values, enum can be used:

"rule": {
  "effect": "HIDE",
  "condition": {
    "scope": "#/properties/name",
    "schema": { enum: ["foo", "bar"] }
  }
}

The rule evaluates to true if the scope property name is either "foo" or "bar".

A rule can be negated using "not":

"rule": {
  "effect": "SHOW",
  "condition": {
    "scope": "#/properties/counter",
    "schema": { not: { const: 10 } }
  }
}

The following rule evaluates to true if the counter property is 1 <= counter < 10:

"rule": {
  "effect": "SHOW",
  "condition": {
    "scope": "#/properties/counter",
    "schema": {  minimum: 1, exclusiveMaximum: 10 }
  }
}

A rule can even operate on the full form data object and over multiple properties:

"rule": {
  "effect": "SHOW",
  "condition": {
    "scope": "#",
    "schema": {
      "properties": {
        "stringArray": { "contains": { "const": "Foo"  }  }
      },
      "required": ["stringArray", "otherProperty"]
    }
  }
}

In this example, the condition is true if the properties "stringArray" and "otherProperty" are set in the form data and the "stringArray" property contains an element "Foo". Note, that the schema rule in this example looks more like a normal JSON schema as it is commonly used.

Rules for input state

The library has a service responsible for handling the input state ("SHOW"/"HIDE" and "DISABLE"/"ENABLE").

To use it you need to follow the steps:

  1. Import and resolve the FormRules service.
  2. Initialize context listener.
  3. Listen for status updates provided by the service and adjust your component.

Remember to dispose the context if it is not used anymore.

Example:

import { FormRules } from '@wincc-oa/wui-forms/services/form-rules/form-rules.service.js';
import { FormComponent } from '@wincc-oa/wui-forms/components/form-component.js';
import { type RendererConfig } from '@wincc-oa/wui-forms/interfaces/renderer-config.js';
import { LitElement, html } from 'lit';
import { customElement, property } from 'lit/decorators.js';

@customElement('custom-input-renderer')
export CustomInputRenderer extends FormComponent {
  ...
  @property({ type: Object }) config?: RendererConfig;
  @property({ type: Boolean, reflect: true }) disabled?: boolean;

  private formRules = container.resolve(FormRules);
  private subscriptions = new Subscription();

  disconnectedCallback() {
    this.subscriptions.unsubscribe();
    this.formRules.disposeContext();
  }

  connectedCallback() {
    const { element } = this.config;
    if (element?.rule) {
        this.formRules.initContext(this, element);
        this.subscriptions.add(
          this.formRules.status$.subscribe(([isDisabled, isVisible]) => {
            this.disabled = isDisabled || !isVisible;
            this.style.display = !isVisible ? 'none' : '';
          })
        );
      }
  }

  render() {
    return html`
      <input ?disabled=${this.disabled}>
    `;
  }

  override disconnectedCallback(): void {
    super.disconnectedCallback();
    this.subscriptions.unsubscribe();
  }
}

Rules for an array element

When working with array elements, you need to provide context for the rules service to validate rules per array item. The context property enables the rules engine to evaluate conditions against the correct data scope for each array element.

Context Types

You can use either:

  • Built-in context: "series" for series array elements
  • Custom lit-context object for your own array implementations

Example: Series Array with Rules

UI Schema:

{
  "type": "SeriesGroup",
  "scope": "#/properties/dataSeries",
  "elements": [
    {
      "type": "CheckboxControl",
      "label": "Enable custom configuration",
      "scope": "#/properties/enableCustomConfig"
    },
    {
      "type": "Control",
      "label": "Custom property",
      "scope": "#/properties/customProperty",
      "options": {
        "context": "series" // Links to wuiSeriesItemContext
      },
      "rule": {
        "effect": "ENABLE",
        "condition": {
          "scope": "#/properties/enableCustomConfig", // Relative to context root
          "schema": {
            "const": true
          }
        }
      }
    }
  ]
}

Series Item Implementation:

import { provide } from '@lit/context';
import { TemplateResult, html } from 'lit';
import { customElement } from 'lit/decorators.js';
import { wuiSeriesItemContext } from '../../contexts/wui-series-context';
import { FormComponent } from '../form-component';

@customElement('wui-series-item')
export class WuiSeriesItem extends FormComponent {
  @provide({ context: wuiSeriesItemContext })
  private seriesItemContext?: string | Record<string, unknown>;

  protected override render(): TemplateResult {
    return html`<slot @input=${this.handleInput}></slot>`;
  }

  private async handleInput(): Promise<void> {
    await this.updateComplete;

    const inputs = this.querySelectorAll<HTMLInputElement | HTMLSelectElement>('[name]');

    // Update context on every change - this triggers rule re-evaluation
    this.seriesItemContext = [...inputs].reduce((total: { [key: string]: unknown }, { name, value, disabled }) => {
      if (!disabled) {
        total[name] = value;
      }
      return total;
    }, {});
  }
}

Custom Context Implementation

For custom array elements, create your own context:

import { createContext } from '@lit/context';

export interface CustomArrayItemContext {
  [key: string]: unknown;
}

export const customArrayItemContext = createContext<CustomArrayItemContext>('custom-array-item');

@customElement('wui-custom-array-item')
export class WuiCustomArrayItem extends FormComponent {
  @provide({ context: customArrayItemContext })
  private itemContext?: CustomArrayItemContext;

  // Update context when form data changes
  private updateContext(formData: Record<string, unknown>): void {
    this.itemContext = formData;
  }
}

Key Points:

  • Context must be updated whenever form data changes
  • Rule scopes are relative to the context root object
  • Each array item maintains its own context for independent rule evaluation
  • The options.context property links the form element to the appropriate context provider

Layout renderers

There are 4 renderers to handle groups of multiple inputs:

WuiLayout

Adds styling to the group of elements. However, the form value is not grouped.

Example:

// UI Schema
{
  "type": "WuiLayout",
  "label": "Full name",
  "elements": [
    {
      "type": "Control",
      "label": "First name",
      "scope": "#/properties/firstName",
    },
    {
      "type": "Control",
      "label": "Last name",
      "scope": "#/properties/lastName",
    }
  ]
},
// Form result
{
  firstName: 'John',
  lastName: 'Doe'
}

HorizontalGroup

Adds styling to the group of elements and adds name property to the group, so in the form the results will be grouped.

Example:

// UI Schema
{
  "type": "HorizontalGroup",
  "label": "Full name",
  "scope": "#/properties/fullName",
  "elements": [
    {
      "type": "Control",
      "label": "First name",
      "scope": "#/properties/firstName",
    },
    {
      "type": "Control",
      "label": "Last name",
      "scope": "#/properties/lastName",
    }
  ]
},
// Form result
{
  fullName: {
    firstName: 'John',
    lastName: 'Doe'
  }
}

VerticalGroup

The same as HorizontalGroup but the items are displayed in column.

SeriesGroup

With this group renderer you will be able to create a list of elements with the same structure.

Example:

// UI Schema
{
  "type": "SeriesGroup",
  "scope": "#/properties/participants",
  "label": "List of participants",
  "elements": [
    {
      "type": "Control",
      "label": "First name",
      "scope": "#/properties/firstName",
    },
    {
      "type": "Control",
      "label": "Last name",
      "scope": "#/properties/lastName",
    }
  ]
}
// Form result
{
  participants: [
    { firstName: 'John', lastName: 'Doe' }
  ]
}

JSON Schema description

Object should comply to the JSONSchema interface from @jsonforms/core library.

For more details please refer to their repository.

type PropertyType = 'string' | 'number' | 'object' | 'array' | 'boolean' | 'null';

interface Property {
  type: PropertyType;
  // validation properties that are used for example in the input-renderer (built-in form validation)
  minimum?: number;
  maximum?: number;
  minLength?: number;
  maxLength?: number;
  // available values for the property
  enum?: unknown[];
  // nested properties
  properties?: { [key: string]: Property };
}

interface JSONSchema {
  type: PropertyType;
  properties: { [key: string]: Property };
  // list of required properties for example - ['propertyName.nestedPropertyName']
  required?: string[];
}

JSON UI Schema description

Object should comply to the Layout definition from @jsonforms/core library.

Renderers

Renderers are components responsible for rendering the elements of your form.

To create renderer you need a class extending the FormComponent.

FormComponent interface

While creating a class for the renderer it is recommended to extend the FormComponent to be sure that everything works correctly with other elements of the form.

// data representation for the custom element
interface Data {
  value: number;
  label: string;
}

@customElement('wui-custom-renderer')
export class WuiCustomRenderer extends FormComponent<Data> {
  /**
   * required to anchor the validation message assigned in the parent
   * for instance if the child renderer has multiple inputs the parent should know which has validation error
   */
  anchor?: HTMLElement;


  ...

  protected override render(): TemplateResult {
    return html``;
  }
}

Validation of a child component

If due to the ShadowDOM or any other reason it is not possible to handle the validation automatically you can still handle this manually.

Here you can find a simplified example of how to handle such cases:


@customElement('wui-parent-with-shadow-dom')
class ParentWithShadowDom extends FormComponent {

  ...

  protected override render(): TemplateResult {
    return html`
      <wui-child-input [name]="input"></wui-child-input>
    `;
  }

  private setValidity(): void {
    let anchor;
    const input = this.shadowRoot?.querySelector('[name="input"]');


    this.validationMessage = input.validationMessage;
    this.validity = input.validity;
    // if the child is a single input it should be enough to provide the whole element
    // however, if there are more than 1 inputs inside, you need to provide an anchor
    // to the element that is invalid, so the error message is presented accordingly
    anchor = input.anchor || input;

    // internals are initialized in the FormComponent class
    this.internals.setValidity(this.validity, this.validationMessage, anchor);
  }
}

Variables

User can define variables for the widget. Currently, the list of variables is static, defined in the [widgetName]-ui-schema.json.

Define an element with scope set to "#/properties/variables". Inside define UI elements with the scope set to variable names - "#/properties/variableName". As a next step define variables you want to use in the options for any other control.

// NOTE - currently only works for DataPointControl
{
  "type": "object",
  "elements": [
    {
      "type": "Tab",
      "scope": "#/properties/variables", // important to make it possible to save the variables properly
      "elements": [
        {
          "type": "WuiLayout",
          "label": "Variables",
          "elements": [
            {
              "scope": "#/properties/sTimeRange", // variable name
              "type": "TimeRangeControl"
            }
          ]
        }
      ]
    },
    {
      "type": "WuiLayout",
      "label": "WUI_Settings.Area.Content",
      "elements": [
        {
          "type": "SeriesGroup",
          "scope": "#/properties/series",
          "label": "Series",
          "elements": [
            {
              "type": "DataPointControl",
              "scope": "#/properties/datapoint",
              "label": "Datapoint",
              "options": {
                "fetchMethod": "historic",
                "variables": {
                  "historic": {
                    "sTimeRange": "${sTimeRange}" // historic.sTimeRange will use a variable
                  }
                }
              }
            }
          ]
        }
      ]
    }
  ]
}

License

MIT