Skip to content

KevinGliewe/jsonforms.lit

Repository files navigation

jsonforms-lit

Native Lit / web-component binding for JSON Forms.

JSON Forms ships official bindings for React, Angular, and Vue. This repository adds a binding for Lit so that JSON Forms can be embedded in any web-component based application with no React/Vue/Angular dependency.

The binding reuses @jsonforms/core unchanged: state management, AJV validation, ranked-tester dispatch, and the state-to-props mappers all come from the upstream core package. Only the framework-specific glue (store hosting, context distribution, dispatch element, reactive controllers) lives here.

Packages

Package Description
@jsonforms-lit/core Framework infrastructure: <json-forms> element, <jf-dispatch-renderer>, JsonFormsStore, @lit/context definitions, reactive controllers.
@jsonforms-lit/vanilla-renderers Unstyled renderer set: text / number / boolean / date / enum controls, vertical / horizontal / group layouts, array & object renderers.
apps/demo Vite-powered runnable demo that wires the two packages to a handful of example schemas.

Quick start

pnpm install
pnpm -r build
pnpm dev            # starts the demo app on http://localhost:5173

Minimal usage

Both import '@jsonforms-lit/core' and import '@jsonforms-lit/vanilla-renderers' are side-effect imports — each module's top-level code calls customElements.define(...) to register its tags. Include them both at least once in your app entry. Without them, document.createElement('json-forms') returns a generic HTMLElement and nothing renders.

import '@jsonforms-lit/core';
import '@jsonforms-lit/vanilla-renderers';
import { vanillaRenderers } from '@jsonforms-lit/vanilla-renderers';

const el = document.createElement('json-forms');
el.schema = {
  type: 'object',
  properties: {
    name: { type: 'string' },
    age: { type: 'integer', minimum: 0 },
  },
  required: ['name'],
};
el.data = { name: 'Alice', age: 30 };
el.renderers = vanillaRenderers;
el.addEventListener('change', (e) => console.log(e.detail.data));
document.body.appendChild(el);

How it fits together

  • JsonFormsStore wraps the core coreReducer with a tiny pub/sub. It is the single source of truth for data, schema, uischema, and validation errors.
  • <json-forms> provides the store and renderer registry via @lit/context so every descendant (even across shadow DOM boundaries) can pull state without prop drilling.
  • <jf-dispatch-renderer> evaluates every registered RankedTester and imperatively instantiates the highest-ranked renderer by tag name, caching the resulting element so data updates don't rebuild the DOM subtree.
  • Reactive controllers (ControlController, LayoutController, ArrayController) subscribe to the store on behalf of renderer elements and expose ready-to-use ControlProps / LayoutProps by calling mapStateToControlProps / mapStateToLayoutProps from @jsonforms/core.

Writing a custom renderer

Three authoring styles are supported. All three use the same dispatch mechanism — pick whichever fits your scope.

  1. Reactive controller — the most flexible path. A single element can combine ControlController with, say, an I18nController or a ValidationController because reactive controllers compose without the multiple-inheritance headache of class-based bases. See packages/vanilla-renderers/src/base/VanillaControlBase.ts for a real-world example.
  2. ControlMixin / LayoutMixin — a drop-in ergonomic mixin that instantiates the controller for you and copies the mapped props onto the host as reactive @state fields. Templates stay terse: <input .value=${this.controlData}>. Trade-off: you lose the compositional flexibility of multiple controllers per element.
  3. Manual store access — if you need to bypass the helpers, consume jsonFormsStoreContext directly via @lit/context and call mapStateToControlProps yourself. This is the same code path the controllers use under the hood.

See packages/core/README.md for copy-paste examples of each style.

Once your element class is defined, register it via:

import { registerRenderer } from '@jsonforms-lit/core';
import { rankWith, isStringControl } from '@jsonforms/core';

export const myEntry = registerRenderer(
  'my-text-control',
  rankWith(1, isStringControl),
  MyTextControl,
);

// Then in your app:
form.renderers = [myEntry, ...vanillaRenderers];

The ranked-tester dispatch prefers the highest score, so a rank of 10 will always win against the vanilla pack's rank 13.

Development

pnpm install
pnpm typecheck
pnpm -r build
pnpm test
pnpm dev

Library packages (core, vanilla-renderers) emit plain ESM via tsc. The demo app is built with Vite. All tests run on node:test via tsx, with a happy-dom shim installed before any Lit module is loaded — so real custom-element upgrades and @lit/context event propagation work without a browser harness.

Test coverage highlights:

  • JsonFormsStore: init, dispatch, subscribe, unsubscribe, validation flow, readonly / config helpers (9 tests).
  • Controllers: ControlController (5), LayoutController (3), ArrayController (3) — including addItem / removeItems coverage.
  • <jf-dispatch-renderer>: tester ranking, unknown-renderer fallback, element caching across data updates, tag swap on uischema change (4 tests).
  • <json-forms> integration: context propagation across shadow DOM boundaries, user-input round-trip through the store, AJV error surfacing, external data reset, internal-vs-external update distinction (5 tests).
  • Mixins: ControlMixin populates label/data/required/errors, forwards edits via handleChange; LayoutMixin exposes layoutElements / layoutDirection and renders child dispatchers (5 tests).
  • registerRenderer: idempotent custom-element definition, registry entry returned (2 tests).
  • Vanilla renderers: text, boolean, number, integer, enum, vertical layout, array add/remove/render, object renderer, group layout, tabbed categorization, multi-line, date (17 tests).

Run the full suite from the workspace root:

pnpm -r test      # 36 core + 17 vanilla-renderers = 53 tests

License

MIT

About

Lit bindings for JSON Forms

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors