vanjs-jsf 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -201
- package/README.md +344 -134
- package/dist/VanJsfField.d.ts +2 -1
- package/dist/VanJsfField.js +21 -2
- package/dist/VanJsfForm.js +7 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +2 -2
- package/dist/jsf-defaults.css +42 -42
- package/dist/theme.d.ts +2 -0
- package/package.json +56 -56
package/README.md
CHANGED
|
@@ -1,134 +1,344 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
**Generate dynamic UI forms effortlessly with TypeScript and JSON Schema, powered by the JSON Schema Form Headless UI framework.**
|
|
4
|
-
|
|
5
|
-
## Description
|
|
6
|
-
|
|
7
|
-
This library
|
|
8
|
-
|
|
9
|
-
### Features
|
|
10
|
-
|
|
11
|
-
- [x] **Dynamic Form Generation**: Create forms directly from JSON Schema, reducing repetitive coding tasks.
|
|
12
|
-
- [x] **
|
|
13
|
-
- [x] **
|
|
14
|
-
- [x] **
|
|
15
|
-
- [x] **
|
|
16
|
-
- [
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
1
|
+
# VanJS-JSF — a JSON Schema Form Library for VanJS
|
|
2
|
+
|
|
3
|
+
**Generate dynamic UI forms effortlessly with TypeScript and JSON Schema, powered by the JSON Schema Form Headless UI framework.**
|
|
4
|
+
|
|
5
|
+
## Description
|
|
6
|
+
|
|
7
|
+
This library provides a robust and flexible solution for dynamically generating user interface (UI) forms using JSON Schema definitions. It is built on **TypeScript** for type safety and leverages the powerful [JSON Schema Form Headless UI](https://github.com/remoteoss/json-schema-form) framework, which you can find documented [here](https://json-schema-form.vercel.app). It ensures a lightweight, modern, accessible, and customizable form-building experience.
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- [x] **Dynamic Form Generation**: Create forms directly from JSON Schema, reducing repetitive coding tasks.
|
|
12
|
+
- [x] **Theming API**: Inject CSS classes for every form element via a `theme` object — works with Tailwind, Bootstrap, or any CSS framework.
|
|
13
|
+
- [x] **Layout Map**: Control per-field spatial distribution (e.g. grid column spans) without coupling CSS classes to JSON Schema.
|
|
14
|
+
- [x] **Customizable**: Tailor form styles, layouts, and behaviours to meet specific UI/UX requirements.
|
|
15
|
+
- [x] **Headless UI Integration**: Utilize Headless UI's components for accessible and modern interfaces.
|
|
16
|
+
- [x] **TypeScript-first Approach**: Enjoy strong typing and enhanced developer experience with TypeScript.
|
|
17
|
+
- [x] **Validation Support**: Easily integrate JSON Schema-based validation for seamless user input handling.
|
|
18
|
+
- [ ] **Extensible Architecture**: Add custom widgets, field types, and behaviours as needed.
|
|
19
|
+
|
|
20
|
+
### Available components
|
|
21
|
+
|
|
22
|
+
The currently supported form element types are:
|
|
23
|
+
|
|
24
|
+
- text = "text"
|
|
25
|
+
- number = "number"
|
|
26
|
+
- textarea = "textarea"
|
|
27
|
+
- select = "select"
|
|
28
|
+
- radio = "radio"
|
|
29
|
+
- date = "date" (Pikaday)
|
|
30
|
+
- code = "code" (CodeMirror with JSON, JavaScript, TypeScript support)
|
|
31
|
+
- fieldset = "fieldset"
|
|
32
|
+
- file = "file" (drag & drop, with configurable `readAs` mode and size validation)
|
|
33
|
+
|
|
34
|
+
## Getting Started
|
|
35
|
+
|
|
36
|
+
1. Install the library:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm install vanjs-jsf
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
2. Import and define your JSON Schema with `x-jsf-presentation` hints:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import van from "vanjs-core";
|
|
46
|
+
import { jsform } from "vanjs-jsf";
|
|
47
|
+
|
|
48
|
+
const { div, h1, p, button } = van.tags;
|
|
49
|
+
|
|
50
|
+
const schema = {
|
|
51
|
+
type: "object",
|
|
52
|
+
properties: {
|
|
53
|
+
userName: {
|
|
54
|
+
type: "string",
|
|
55
|
+
title: "Name",
|
|
56
|
+
"x-jsf-presentation": { inputType: "text" },
|
|
57
|
+
},
|
|
58
|
+
age: {
|
|
59
|
+
type: "number",
|
|
60
|
+
title: "Age",
|
|
61
|
+
"x-jsf-presentation": { inputType: "number" },
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
required: ["userName"],
|
|
65
|
+
"x-jsf-order": ["userName", "age"],
|
|
66
|
+
};
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
3. Create a config with initial values and render the form:
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
const initialValues = { userName: "Simon" };
|
|
73
|
+
const config = {
|
|
74
|
+
strictInputType: false,
|
|
75
|
+
initialValues: initialValues,
|
|
76
|
+
formValues: initialValues,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const handleOnSubmit = (e: Event) => {
|
|
80
|
+
e.preventDefault();
|
|
81
|
+
const values = config.formValues;
|
|
82
|
+
alert(`Submitted: ${JSON.stringify(values, null, 2)}`);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
van.add(
|
|
86
|
+
document.body,
|
|
87
|
+
div(
|
|
88
|
+
h1("json-schema-form + VanJS"),
|
|
89
|
+
p("Dynamic form generated from JSON Schema."),
|
|
90
|
+
jsform(
|
|
91
|
+
{
|
|
92
|
+
name: "my-jsf-form",
|
|
93
|
+
schema: schema,
|
|
94
|
+
config: config,
|
|
95
|
+
onsubmit: handleOnSubmit,
|
|
96
|
+
},
|
|
97
|
+
button({ type: "submit" }, "Submit"),
|
|
98
|
+
),
|
|
99
|
+
),
|
|
100
|
+
);
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## File Upload Field
|
|
104
|
+
|
|
105
|
+
The `file` field type renders a drag & drop zone with file info display. Define it in your schema:
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
const schema = {
|
|
109
|
+
type: "object",
|
|
110
|
+
properties: {
|
|
111
|
+
document: {
|
|
112
|
+
type: "string",
|
|
113
|
+
title: "Upload Document",
|
|
114
|
+
description: "PDF or image, max 5 MB",
|
|
115
|
+
"x-jsf-presentation": {
|
|
116
|
+
inputType: "file",
|
|
117
|
+
accept: ".pdf,.png,.jpg",
|
|
118
|
+
maxSizeMB: 5,
|
|
119
|
+
readAs: "auto",
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
"x-jsf-order": ["document"],
|
|
124
|
+
};
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### `readAs` modes
|
|
128
|
+
|
|
129
|
+
| Value | Behaviour |
|
|
130
|
+
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
131
|
+
| `"auto"` (default) | Text extensions (json, csv, txt, xml, yaml, md, html, css, js, ts, sql, etc.) are read as text. All other files are read as binary and returned as base64. |
|
|
132
|
+
| `"text"` | Always reads the file as text (`readAsText`). |
|
|
133
|
+
| `"dataURL"` | Returns a data URL string (`readAsDataURL`). |
|
|
134
|
+
| `"arrayBuffer"` | Reads as ArrayBuffer and returns base64 (`readAsArrayBuffer`). |
|
|
135
|
+
|
|
136
|
+
The file content is stored in `formValues` under the field name. Additional metadata is stored as `<fieldName>__fileName`, `<fieldName>__fileSize`, and `<fieldName>__fileType`.
|
|
137
|
+
|
|
138
|
+
## Theming
|
|
139
|
+
|
|
140
|
+
By default, vanjs-jsf applies minimal semantic CSS classes (e.g. `jsf-dropzone`, `jsf-file-info`). You can import the default styles:
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import "vanjs-jsf/dist/jsf-defaults.css";
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
To fully customize the appearance, pass a `theme` object to `jsform()`. Theme classes apply to every element type in the form. Per-field classes defined in `x-jsf-presentation` (e.g. `class`, `titleClass`, `containerClass`) take priority over theme classes.
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
import { jsform, JsfTheme } from "vanjs-jsf";
|
|
150
|
+
|
|
151
|
+
const myTheme: JsfTheme = {
|
|
152
|
+
// Structure
|
|
153
|
+
container: "mb-4",
|
|
154
|
+
label: "block text-sm font-medium text-gray-700 mb-1",
|
|
155
|
+
description: "text-gray-500 text-xs mb-1",
|
|
156
|
+
error: "text-red-500 text-xs mt-1",
|
|
157
|
+
requiredIndicator: "text-red-500 ml-1",
|
|
158
|
+
|
|
159
|
+
// Inputs
|
|
160
|
+
input: "w-full border border-gray-300 rounded px-3 py-2",
|
|
161
|
+
textarea: "w-full border border-gray-300 rounded px-3 py-2 resize-y",
|
|
162
|
+
select: "w-full border border-gray-300 rounded px-3 py-2",
|
|
163
|
+
option: "bg-white",
|
|
164
|
+
|
|
165
|
+
// Radio
|
|
166
|
+
radioGroup: "flex flex-col gap-2",
|
|
167
|
+
radioLabel: "flex items-center gap-2 cursor-pointer",
|
|
168
|
+
radioInput: "accent-blue-500",
|
|
169
|
+
|
|
170
|
+
// Fieldset
|
|
171
|
+
fieldset: "border border-gray-200 rounded p-4",
|
|
172
|
+
legend: "text-sm font-semibold text-gray-700",
|
|
173
|
+
|
|
174
|
+
// File upload
|
|
175
|
+
dropZone:
|
|
176
|
+
"border-2 border-dashed border-gray-300 rounded-lg p-6 text-center cursor-pointer hover:border-blue-400 transition-colors",
|
|
177
|
+
dropZoneActive:
|
|
178
|
+
"border-2 border-dashed border-blue-500 rounded-lg p-6 text-center cursor-pointer bg-blue-50",
|
|
179
|
+
dropZoneText: "text-gray-500 text-sm m-0",
|
|
180
|
+
fileInfoBar: "mt-2 flex items-center gap-2",
|
|
181
|
+
fileName: "font-semibold",
|
|
182
|
+
fileSize: "text-gray-400 text-sm",
|
|
183
|
+
fileClearButton:
|
|
184
|
+
"text-sm border border-gray-300 rounded px-2 py-0.5 hover:bg-gray-100",
|
|
185
|
+
fileReading: "mt-2 text-gray-400 text-sm",
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const formEl = jsform({
|
|
189
|
+
schema: mySchema,
|
|
190
|
+
config: { initialValues: {}, formValues: {} },
|
|
191
|
+
theme: myTheme,
|
|
192
|
+
onsubmit: handleSubmit,
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### JsfTheme properties
|
|
197
|
+
|
|
198
|
+
| Property | Applies to |
|
|
199
|
+
| ------------------- | ------------------------------------------------------ |
|
|
200
|
+
| `container` | Wrapper `<div>` of each field |
|
|
201
|
+
| `label` | `<label>` elements |
|
|
202
|
+
| `description` | Description `<div>` below the label |
|
|
203
|
+
| `error` | Error `<p>` below the input |
|
|
204
|
+
| `requiredIndicator` | `<span>` with "\*" next to required field labels |
|
|
205
|
+
| `input` | `<input>` for text, number, and date fields |
|
|
206
|
+
| `textarea` | `<textarea>` elements |
|
|
207
|
+
| `select` | `<select>` elements |
|
|
208
|
+
| `option` | `<option>` elements inside selects |
|
|
209
|
+
| `radioGroup` | Container `<div>` for radio options |
|
|
210
|
+
| `radioLabel` | `<label>` wrapping each radio option |
|
|
211
|
+
| `radioInput` | `<input type="radio">` elements |
|
|
212
|
+
| `fieldset` | `<fieldset>` elements |
|
|
213
|
+
| `legend` | `<legend>` elements (falls back to `label` if not set) |
|
|
214
|
+
| `dropZone` | File drop zone container (normal state) |
|
|
215
|
+
| `dropZoneActive` | File drop zone during dragover |
|
|
216
|
+
| `dropZoneText` | Text inside the drop zone |
|
|
217
|
+
| `fileInfoBar` | Container for uploaded file info |
|
|
218
|
+
| `fileName` | File name `<strong>` |
|
|
219
|
+
| `fileSize` | File size `<small>` |
|
|
220
|
+
| `fileClearButton` | "Clear" button |
|
|
221
|
+
| `fileReading` | "Reading file..." indicator |
|
|
222
|
+
|
|
223
|
+
## Layout
|
|
224
|
+
|
|
225
|
+
The `layout` attribute lets you assign extra CSS classes to specific field containers by name, without modifying the JSON Schema. This is useful for controlling column spans in CSS grid layouts.
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
import { jsform, JsfTheme, JsfLayout } from "vanjs-jsf";
|
|
229
|
+
|
|
230
|
+
const theme: JsfTheme = {
|
|
231
|
+
container: "mb-4",
|
|
232
|
+
input: "w-full border rounded px-3 py-2",
|
|
233
|
+
label: "block text-sm font-medium mb-1",
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const layout: JsfLayout = {
|
|
237
|
+
host: "col-span-2", // full row
|
|
238
|
+
dbname: "col-span-2", // full row
|
|
239
|
+
// port, user, password, schema → no entry → default 1 column
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const formEl = jsform({
|
|
243
|
+
schema: connectionSchema,
|
|
244
|
+
config: { initialValues: {}, formValues: {} },
|
|
245
|
+
theme,
|
|
246
|
+
layout,
|
|
247
|
+
});
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Wrap the form in a grid container to activate the column distribution:
|
|
251
|
+
|
|
252
|
+
```css
|
|
253
|
+
.jsf-grid > form {
|
|
254
|
+
display: grid;
|
|
255
|
+
grid-template-columns: 1fr 1fr;
|
|
256
|
+
gap: 0 1rem;
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
const { div } = van.tags;
|
|
262
|
+
van.add(document.body, div({ class: "jsf-grid" }, formEl));
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
This produces a layout like:
|
|
266
|
+
|
|
267
|
+
```text
|
|
268
|
+
┌─────────────── host ────────────────┐
|
|
269
|
+
│ 192.168.1.176 │
|
|
270
|
+
├────── port ──────┬──── user ────────┤
|
|
271
|
+
│ 5432 │ wolfops │
|
|
272
|
+
├──── password ────┬──── schema ──────┤
|
|
273
|
+
│ •••••••• │ public │
|
|
274
|
+
├─────────────── dbname ──────────────┤
|
|
275
|
+
│ wolfops_dev │
|
|
276
|
+
└─────────────────────────────────────┘
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Nested fields
|
|
280
|
+
|
|
281
|
+
For fields inside a fieldset, `layout` supports both the full path and the short name:
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
const layout: JsfLayout = {
|
|
285
|
+
"connection.host": "col-span-2", // by full path
|
|
286
|
+
port: "col-span-1", // by short name (also works for nested)
|
|
287
|
+
};
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
The full path (`fieldset.fieldName`) is checked first, then the short name.
|
|
291
|
+
|
|
292
|
+
### Class resolution order
|
|
293
|
+
|
|
294
|
+
For each element, the resolved class follows this priority:
|
|
295
|
+
|
|
296
|
+
1. **Per-field class** from `x-jsf-presentation` (e.g. `class`, `titleClass`, `containerClass`)
|
|
297
|
+
2. **Theme class** from the `theme` object
|
|
298
|
+
3. **Empty string** (no class applied)
|
|
299
|
+
|
|
300
|
+
Layout classes are appended *after* the resolved container class, so they combine with both per-field and theme classes.
|
|
301
|
+
|
|
302
|
+
### Visibility
|
|
303
|
+
|
|
304
|
+
Hidden fields receive the `jsf-hidden` CSS class instead of inline `display: none`. Make sure your CSS includes:
|
|
305
|
+
|
|
306
|
+
```css
|
|
307
|
+
.jsf-hidden {
|
|
308
|
+
display: none;
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
This is included in `jsf-defaults.css`.
|
|
313
|
+
|
|
314
|
+
## Development
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
npm install # Install dependencies
|
|
318
|
+
npm run dev # Start Vite dev server (port 3030)
|
|
319
|
+
npm run build # Bundle with esbuild → dist/index.js
|
|
320
|
+
npm run types # Generate type declarations → dist/*.d.ts
|
|
321
|
+
npm run lint # Run ESLint
|
|
322
|
+
npm run lint:fix # Run ESLint with auto-fix
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
## Publishing
|
|
326
|
+
|
|
327
|
+
1. Update the version in `package.json`
|
|
328
|
+
2. Run the publish script with your npm OTP:
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
./publish.sh <otp>
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
This cleans `dist/`, rebuilds the bundle and type declarations, copies `jsf-defaults.css` to `dist/`, and publishes to npm.
|
|
335
|
+
|
|
336
|
+
The package is available at: https://www.npmjs.com/package/vanjs-jsf
|
|
337
|
+
|
|
338
|
+
## Contributing
|
|
339
|
+
|
|
340
|
+
Contributions are welcome! Please submit a pull request if you have ideas, feedback, or improvements. Your contributions will help make this library more robust and useful for the community.
|
|
341
|
+
|
|
342
|
+
## License
|
|
343
|
+
|
|
344
|
+
This project is licensed under the Apache 2 License. For details, see the [LICENSE](./LICENSE) file.
|
package/dist/VanJsfField.d.ts
CHANGED
|
@@ -16,11 +16,12 @@ export declare class VanJsfField extends VanJSComponent {
|
|
|
16
16
|
isVisibleState: State<boolean>;
|
|
17
17
|
errorState: State<string>;
|
|
18
18
|
theme: JsfTheme;
|
|
19
|
+
layoutClass: string;
|
|
19
20
|
/** Used by file fields to pass file metadata to formValues */
|
|
20
21
|
fileNameValue: string;
|
|
21
22
|
fileSizeValue: string;
|
|
22
23
|
fileTypeValue: string;
|
|
23
|
-
constructor(field: Record<string, unknown>, initVal: MultiType, handleChange: (field: VanJsfField, value: MultiType) => void, theme?: JsfTheme);
|
|
24
|
+
constructor(field: Record<string, unknown>, initVal: MultiType, handleChange: (field: VanJsfField, value: MultiType) => void, theme?: JsfTheme, layoutClass?: string);
|
|
24
25
|
get inputType(): string;
|
|
25
26
|
get label(): string;
|
|
26
27
|
get class(): string;
|
package/dist/VanJsfField.js
CHANGED
|
@@ -12,6 +12,7 @@ const { div, p, input, label, textarea, legend, link, fieldset, span, select, op
|
|
|
12
12
|
var FieldType;
|
|
13
13
|
(function (FieldType) {
|
|
14
14
|
FieldType["text"] = "text";
|
|
15
|
+
FieldType["password"] = "password";
|
|
15
16
|
FieldType["code"] = "code";
|
|
16
17
|
FieldType["number"] = "number";
|
|
17
18
|
FieldType["textarea"] = "textarea";
|
|
@@ -44,17 +45,19 @@ export class VanJsfField extends VanJSComponent {
|
|
|
44
45
|
isVisibleState;
|
|
45
46
|
errorState;
|
|
46
47
|
theme;
|
|
48
|
+
layoutClass;
|
|
47
49
|
/** Used by file fields to pass file metadata to formValues */
|
|
48
50
|
fileNameValue = "";
|
|
49
51
|
fileSizeValue = "";
|
|
50
52
|
fileTypeValue = "";
|
|
51
|
-
constructor(field, initVal, handleChange, theme = {}) {
|
|
53
|
+
constructor(field, initVal, handleChange, theme = {}, layoutClass = "") {
|
|
52
54
|
super();
|
|
53
55
|
this.field = field;
|
|
54
56
|
this.name = field.name;
|
|
55
57
|
this.iniVal = initVal;
|
|
56
58
|
this.handleChange = handleChange;
|
|
57
59
|
this.theme = theme;
|
|
60
|
+
this.layoutClass = layoutClass;
|
|
58
61
|
this.isVisibleState = van.state(this.field.isVisible);
|
|
59
62
|
this.errorState = van.state("");
|
|
60
63
|
}
|
|
@@ -167,9 +170,10 @@ export class VanJsfField extends VanJSComponent {
|
|
|
167
170
|
render() {
|
|
168
171
|
let el;
|
|
169
172
|
const baseContainer = resolve(this.containerClass, this.theme.container);
|
|
173
|
+
const containerCls = this.layoutClass ? `${baseContainer} ${this.layoutClass}` : baseContainer;
|
|
170
174
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
171
175
|
const props = {
|
|
172
|
-
class: () => this.isVisible ?
|
|
176
|
+
class: () => this.isVisible ? containerCls : `${containerCls} jsf-hidden`.trim(),
|
|
173
177
|
};
|
|
174
178
|
switch (this.inputType) {
|
|
175
179
|
case FieldType.text:
|
|
@@ -181,6 +185,21 @@ export class VanJsfField extends VanJSComponent {
|
|
|
181
185
|
oninput: (e) => this.handleChange(this, e.target.value),
|
|
182
186
|
}), this.renderError());
|
|
183
187
|
break;
|
|
188
|
+
case FieldType.password:
|
|
189
|
+
// Password is rendered with type="password" so the user agent
|
|
190
|
+
// masks the value as it is typed. Otherwise behaves identically
|
|
191
|
+
// to a text input — same theme.input class, same oninput
|
|
192
|
+
// wiring. autocomplete="new-password" hints to browsers that
|
|
193
|
+
// this is a credential entry form, not a "remember me" field.
|
|
194
|
+
el = div(props, this.renderLabel(), this.renderDescription(), input({
|
|
195
|
+
id: this.name,
|
|
196
|
+
type: "password",
|
|
197
|
+
autocomplete: "new-password",
|
|
198
|
+
class: resolve(this.class, this.theme.input),
|
|
199
|
+
value: this.iniVal,
|
|
200
|
+
oninput: (e) => this.handleChange(this, e.target.value),
|
|
201
|
+
}), this.renderError());
|
|
202
|
+
break;
|
|
184
203
|
case FieldType.textarea:
|
|
185
204
|
el = div(props, this.renderLabel(), this.renderDescription(), textarea({
|
|
186
205
|
id: this.name,
|
package/dist/VanJsfForm.js
CHANGED
|
@@ -10,7 +10,8 @@ class VanJsfForm {
|
|
|
10
10
|
formFields;
|
|
11
11
|
formValues;
|
|
12
12
|
theme;
|
|
13
|
-
|
|
13
|
+
layout;
|
|
14
|
+
constructor(jsonSchema, config, isValid, theme = {}, layout = {}) {
|
|
14
15
|
// Bind methods to instance. Needed to pass functions as props to child components
|
|
15
16
|
//this.handleSubmit = this.handleSubmit.bind(this);
|
|
16
17
|
this.handleFieldChange = this.handleFieldChange.bind(this);
|
|
@@ -19,6 +20,7 @@ class VanJsfForm {
|
|
|
19
20
|
this.config = config;
|
|
20
21
|
this.isValid = isValid || undefined;
|
|
21
22
|
this.theme = theme;
|
|
23
|
+
this.layout = layout;
|
|
22
24
|
// Working with parameters
|
|
23
25
|
const initialValues = { ...config?.initialValues };
|
|
24
26
|
this.headlessForm = createHeadlessForm(jsonSchema, config);
|
|
@@ -100,7 +102,8 @@ class VanJsfForm {
|
|
|
100
102
|
field.fields = this.processFields(field.fields, initialValues, formValues, fieldPath);
|
|
101
103
|
}
|
|
102
104
|
// Create and return a new VanJsfField instance for this field
|
|
103
|
-
|
|
105
|
+
const fieldLayout = this.layout[fieldPath] || this.layout[field.name] || "";
|
|
106
|
+
return new VanJsfField(field, initVal, this.handleFieldChange, this.theme, fieldLayout);
|
|
104
107
|
});
|
|
105
108
|
}
|
|
106
109
|
}
|
|
@@ -115,7 +118,8 @@ export function jsform(attributes, ...children) {
|
|
|
115
118
|
config.formValues = {};
|
|
116
119
|
const isValid = attributes.isValid;
|
|
117
120
|
const theme = attributes.theme ?? {};
|
|
118
|
-
const
|
|
121
|
+
const layout = attributes.layout ?? {};
|
|
122
|
+
const vanJsfForm = new VanJsfForm(attributes.schema, config, isValid, theme, layout);
|
|
119
123
|
const fields = vanJsfForm.formFields.map((field) => field.render());
|
|
120
124
|
const childrenWithFields = [...fields, ...children]; // Concatenate fields with other children
|
|
121
125
|
const originalOnSubmit = attributes.onsubmit;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { jsform } from "./VanJsfForm";
|
|
2
|
-
export type { JsfTheme } from "./theme";
|
|
2
|
+
export type { JsfTheme, JsfLayout } from "./theme";
|