Skip to content

Commit b6d0ab6

Browse files
committed
feat: generic widgets port to the 2024 tsx to literal impl., react hook, add some docs
1 parent 5515a10 commit b6d0ab6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1189
-983
lines changed

README.md

Lines changed: 160 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ Swap built-in components with your own, or add custom widget thanks to [UI schem
5353

5454
> [!CAUTION]
5555
> Not for production.
56+
> Code is actually under a major rewrite.
5657
> Expect the doc. to be not in sync. with the actual released code.
5758
5859
![](https://ik.imagekit.io/jc0/jsfe/design/header_json-schema-form-element_2RpVU_W-y-.png?updatedAt=1695289194993)
@@ -104,61 +105,64 @@ Jump to [**UI libraries**](#component-libraries):
104105

105106
<!-- prettier-ignore -->
106107
- [Field types](#field-types)
107-
- [Primitives](#primitives)
108-
- [String](#string)
109-
- [Number](#number)
110-
- [Boolean](#boolean)
111-
- [Enumeration](#enumeration)
112-
- [Date](#date)
113-
- [Object](#object)
114-
- [Additional properties](#additional-properties)
115-
- [Arrays](#arrays)
116-
- [Basic](#basic)
117-
- [Fixed](#fixed)
118-
- [Nested](#nested)
119-
- [Multiple choices (enums.)](#multiple-choices-enums)
120-
- [Additional items](#additional-items)
108+
- [Primitives](#primitives)
109+
- [String](#string)
110+
- [Number](#number)
111+
- [Boolean](#boolean)
112+
- [Enumeration](#enumeration)
113+
- [Date](#date)
114+
- [Object](#object)
115+
- [Additional properties](#additional-properties)
116+
- [Arrays](#arrays)
117+
- [Basic](#basic)
118+
- [Fixed](#fixed)
119+
- [Nested](#nested)
120+
- [Multiple choices (enums.)](#multiple-choices-enums)
121+
- [Additional items](#additional-items)
121122
- [Subschemas](#subschemas)
122-
- [allOf](#allof)
123-
- [oneOf](#oneof)
124-
- [anyOf](#anyof)
123+
- [allOf](#allof)
124+
- [oneOf](#oneof)
125+
- [anyOf](#anyof)
125126
- [Conditionals](#conditionals)
126-
- [Dependencies](#dependencies)
127-
- [If, then, else](#if-then-else)
127+
- [Dependencies](#dependencies)
128+
- [If, then, else](#if-then-else)
128129
- [Miscellaneous](#miscellaneous)
129-
- [References](#references)
130-
- [Recursivity](#recursivity)
131-
- [Nullable values](#nullable-values)
130+
- [References](#references)
131+
- [Recursivity](#recursivity)
132+
- [Nullable values](#nullable-values)
132133
- [User Interface](#user-interface)
133-
- [Schema](#schema)
134+
- [Schema](#schema)
134135
- [Usage](#usage)
135-
- [Installation](#installation)
136-
- [UI Libraries](#ui-libraries)
137-
- [Implementations](#implementations)
138-
- [All examples](#all-examples)
139-
- [Pure HTML with CDN](#pure-html-with-cdn)
140-
- [TypeScript (no framework)](#typescript-no-framework)
141-
- [Astro (SSR)](#astro-ssr)
142-
- [Lit](#lit)
143-
- [Solid](#solid)
144-
- [Vue](#vue)
145-
- [Svelte](#svelte)
146-
- [React](#react)
147-
- [CSS](#css)
148-
- [TypeScript](#typescript)
149-
- [Support for each implementation](#support-for-each-implementation)
136+
- [Installation](#installation)
137+
- [UI Libraries](#ui-libraries)
138+
- [Implementations](#implementations)
139+
- [All examples](#all-examples)
140+
- [Pure HTML with CDN](#pure-html-with-cdn)
141+
- [TypeScript (no framework)](#typescript-no-framework)
142+
- [Astro (SSR)](#astro-ssr)
143+
- [Lit](#lit)
144+
- [Solid](#solid)
145+
- [Vue](#vue)
146+
- [Svelte](#svelte)
147+
- [React](#react)
148+
- [CSS](#css)
149+
- [TypeScript](#typescript)
150+
- [Support for each implementation](#support-for-each-implementation)
150151
- [Component libraries](#component-libraries)
151-
- [Shoelace](#shoelace)
152-
- [Custom widgets](#custom-widgets)
153-
- [Design choices](#design-choices)
152+
- [Shoelace](#shoelace)
153+
- [Custom widgets](#custom-widgets)
154+
- [Design choices](#design-choices)
154155
- [Validation](#validation)
155156
- [Schema massaging](#schema-massaging)
156157
- [Custom Elements Manifests](#custom-elements-manifests)
157158
- [Packages informations](#packages-informations)
158-
- [_Next_ versions](#next-versions)
159+
- [_Next_ versions](#next-versions)
159160
- [Experimental features](#experimental-features)
160161
- [Improvements](#improvements)
161162
- [Acknowledgements](#acknowledgements)
163+
- [Widgets](#widgets)
164+
- [Typings](#typings)
165+
- [Lit SSR](#lit-ssr)
162166

163167
</details>
164168

@@ -1088,3 +1092,117 @@ the ideas RSJF creators brought.
10881092
- [remark-lint-frontmatter-schema](https://github.com/JulianCataldo/remark-lint-frontmatter-schema): Validate your Markdown **frontmatter** data against a **JSON schema**.
10891093
- [retext-case-police](https://github.com/JulianCataldo/retext-case-police): Check popular names casing. Example: ⚠️ `github` → ✅ `GitHub`.
10901094
- [astro-openapi](https://github.com/JulianCataldo/astro-openapi): An Astro toolset for building full-stack operations easily, with type-safety and documentation as first-class citizens.
1095+
1096+
---
1097+
1098+
# 🚧 New documentation 🚧
1099+
1100+
<!-- -->
1101+
1102+
As the API is evolving, for now, examples are mostly Lit focused, but the library works perfectly with almost any kind of UI libraries (or none).
1103+
1104+
- `engine` is UI agnostic. Unit tested.
1105+
- `generics` provide a toolbox of helpers, style-less widgets, and a base form element that can easily be customized.
1106+
- `webawesome` provides a Custom Element pre-loaded with the Webawesome UI library, with a few extras widgets (not native in browsers). It's an extension of the Generic element above.
1107+
1108+
The Custom Elements are not the only way to build with JSFE. They are implementation reference, and with minor modification, they can be ported to any JSX based component.
1109+
1110+
---
1111+
1112+
Quick peek of the new API, we're using Vite here, for the sake of brievety, but special care will follow for React 19, Vue and Solid.
1113+
1114+
```ts
1115+
import { unsafeCSS } from 'lit';
1116+
import styles from '@jsfe/webawesome/css?inline';
1117+
1118+
(class extends JsonSchemaFormWebawesome {
1119+
static override styles = [unsafeCSS(styles)];
1120+
}).define();
1121+
```
1122+
1123+
As you can see, CSS loading is left to the user to handle.
1124+
After many trial and errors, I think it's the most robust way to handle scoped styles presets with total override capabilities for the end user.
1125+
1126+
---
1127+
1128+
You can get pretty far with pure, native HTML inputs, and the `<jsf-genetic>`, too.
1129+
1130+
Trick is to leverage form semantics, and a smart CSS library, like PicoCSS:
1131+
1132+
```ts
1133+
import { JsonSchemaFormGeneric } from '@jsfe/generics';
1134+
import { unsafeCSS, css } from 'lit';
1135+
import picoStyles from '@picocss/pico?inline';
1136+
1137+
(class extends JsonSchemaFormGeneric {
1138+
static override styles = [
1139+
unsafeCSS(picoStyles),
1140+
1141+
// More CSS for you, here!
1142+
css`
1143+
/* DEBUG */
1144+
*:focus {
1145+
outline: 2px solid red !important;
1146+
}
1147+
`,
1148+
];
1149+
}).define();
1150+
```
1151+
1152+
Under the hood, it will override the `LitElement` super-class' static styles, combined with your own mean of CSS optimizations (meaning, your bundler).
1153+
Also, the _JSFE_ API provides class mapping via JS props. for all widgets elements, if you need finer selectors targeting.
1154+
1155+
The _JSFE_ provided Custom Elements always have their `styles` empty.
1156+
Expect some visual breakage for the Webawesome CE if you forget them.
1157+
For the `<jsf-generic>`, it's "broken" visually by default ;) Goal is to let you "paint" it, from scratch, or via Bootstrap, PicoCSS, Flowbite, DaisyUI… leveraging class mapping.
1158+
1159+
## Widgets
1160+
1161+
TODO:
1162+
1163+
<!-- -->
1164+
1165+
## Typings
1166+
1167+
TODO: Typings generation for the Custom Elements is not yet implemented.
1168+
It will be like https://github.com/JulianCataldo/node-flow-elements.
1169+
Pretty cool to get Custom Elements type aware for their props, in many UI frameworks.
1170+
1171+
## Lit SSR
1172+
1173+
_JSFE_ fully supports **server side rendering** via any Lit SSR adapted host (Next.js, Gracile, DIY…).
1174+
1175+
It can render **fully static HTML markup** without any client side JS and the whole form will work just fine, via classic `application/x-www-form-urlencoded` submission.
1176+
1177+
Adding JS hydration for Custom Elements will "augment" your form for more advanced stuff that cannot be made with native widgets, like array manipulation. Finally, for a more progressive user experience, when JS is loaded, _JSFE_ can take over the classic form submission and provide you a window for rolling your own JSON `GET`s or `POST`s.
1178+
1179+
When you want to server render a template, in the light DOM, via **Lit SSR**, **without full page hydration**, and **with or without** Custom Elements hydration, just stringify your seed configurations:
1180+
1181+
```ts
1182+
const Template = html`
1183+
<!-- -->
1184+
1185+
<jsf-webawesome
1186+
schema=${JSON.stringify(schema)}
1187+
ui=${JSON.stringify(ui)}
1188+
data=${JSON.stringify(data)}
1189+
></jsf-webawesome>
1190+
1191+
<!-- -->
1192+
`;
1193+
```
1194+
1195+
If you don't care about serialization, like when you are hydrating the whole page -with the root, light DOM included-, or if you just use _JSFE_ in a client only LitElement:
1196+
1197+
```ts
1198+
const Template = html`
1199+
<!-- -->
1200+
1201+
<jsf-webawesome .schema=${schema} .ui=${ui} .data=${data}></jsf-webawesome>
1202+
1203+
<!-- -->
1204+
`;
1205+
```
1206+
1207+
Properties binding is not obligatory, you can just use plain props. But keep in mind it's not recommended if those bindings are going through high frequency changes.
1208+
Also, the big JSON blobs are bloating the DOM, for no real use for reflection.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { JsonSchemaFormGeneric } from './elements.js';
2+
3+
JsonSchemaFormGeneric.define();

packages/generics/src/elements.tsx

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
'use html-signal';
2+
import type {
3+
EncType,
4+
GenericData,
5+
GenericFormProperties,
6+
NativeFormAttributes,
7+
ReadonlyJSONSchema7,
8+
TagName,
9+
Widgets,
10+
} from '@jsfe/engine';
11+
import type { UiSchema } from '@jsfe/engine';
12+
13+
import { JsonSchemaFormEngine } from '@jsfe/engine';
14+
import { isDev } from '@jsfe/engine/logger';
15+
import { SignalWatcher } from '@lit-labs/signals';
16+
import { LitElement, type PropertyDeclaration } from 'lit';
17+
18+
import { FormGeneric } from './form-generic.jsx';
19+
import { log } from './form.js';
20+
import * as genericWidgets from './widgets/index.js';
21+
22+
/**
23+
* Abstract , Loggerbase class for JSON Schema Form elements.
24+
* Implements native `form` attributes.
25+
*
26+
* @template Schema - The JSON Schema type
27+
* @template Path - The data paths union for form fields
28+
* @template Data - The data type for the whole form object
29+
*/
30+
export abstract class JsonSchemaFormElement<
31+
Schema extends ReadonlyJSONSchema7 | undefined = undefined,
32+
Ui extends UiSchema = UiSchema,
33+
Data extends GenericData = GenericData,
34+
>
35+
extends SignalWatcher(LitElement)
36+
implements GenericFormProperties<Schema, Ui, Data>
37+
{
38+
public static properties: Record<
39+
'data' | 'form' | 'schema' | 'ui' | 'widgets' | keyof NativeFormAttributes,
40+
PropertyDeclaration
41+
> = {
42+
acceptCharset: { attribute: 'accept-charset', type: String },
43+
action: { type: String },
44+
autoComplete: { type: String },
45+
data: { attribute: true, type: Object },
46+
encType: { type: String },
47+
form: { type: Object },
48+
method: { type: String },
49+
name: { type: String },
50+
noValidate: { reflect: true, type: Boolean },
51+
schema: { attribute: true, type: Object },
52+
target: { type: String },
53+
ui: { attribute: true, type: Object },
54+
widgets: { type: Object },
55+
};
56+
57+
public static readonly tagName: TagName = 'json-schema-form';
58+
59+
public acceptCharset?: string;
60+
public action?: string;
61+
public autoComplete?: 'off' | 'on';
62+
public data: Data = {} as Data;
63+
public debug = isDev;
64+
public encType?: EncType;
65+
public form: JsonSchemaFormEngine<Schema, Ui, Data> | undefined;
66+
public method?: 'dialog' | 'get' | 'post';
67+
public name?: string;
68+
public noValidate?: boolean;
69+
public schema: Schema = {} as Schema;
70+
public target?: string;
71+
public ui: Ui = {} as Ui;
72+
public widgets: Partial<Widgets> = {};
73+
74+
/**
75+
* Wraps the native `customElements.define` with defaults, for convenience.
76+
* @param tagName - The name of the custom element
77+
* @param ctor - The constructor for the custom element
78+
*/
79+
public static define(tagName = this.tagName, ctor = this) {
80+
// @ts-expect-error Abstract class constructor
81+
customElements.define(tagName, ctor);
82+
}
83+
84+
/**
85+
* Initialize the form engine with user provided attributes.
86+
* This lifecycle hooks is fired with SSR, too, during the single render pass.
87+
*/
88+
protected willUpdate() {
89+
this.form ??= new JsonSchemaFormEngine(
90+
this.schema,
91+
this.ui,
92+
this.data,
93+
this.widgets,
94+
);
95+
}
96+
}
97+
98+
/**
99+
* Generic implementation of the JSON Schema Form element
100+
*/
101+
export class JsonSchemaFormGeneric extends JsonSchemaFormElement {
102+
public static readonly tagName = 'jsf-generic';
103+
104+
public widgets = genericWidgets;
105+
106+
protected override firstUpdated() {
107+
if (this.debug) {
108+
this.form?.addEventListener('change', () => {
109+
log.debug('change');
110+
});
111+
this.form?.addEventListener('input', () => {
112+
log.debug('input');
113+
});
114+
}
115+
}
116+
117+
protected override render(): unknown {
118+
if (!this.form) {
119+
const message = 'Missing form instance';
120+
log.error(message);
121+
return this.debug ? message : '';
122+
}
123+
124+
return <FormGeneric debug={this.debug} form={this.form} />;
125+
}
126+
}
127+
128+
declare global {
129+
interface HTMLElementTagNameMap {
130+
'jsf-generic': JsonSchemaFormGeneric;
131+
}
132+
}

0 commit comments

Comments
 (0)