vanjs-jsf 0.2.2 → 0.3.1
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/dist/VanJsfField.d.ts +7 -1
- package/dist/VanJsfField.js +57 -41
- package/dist/VanJsfForm.js +6 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js.map +4 -4
- package/dist/jsf-defaults.css +42 -0
- package/dist/theme.d.ts +26 -0
- package/dist/theme.js +2 -0
- package/package.json +1 -1
package/dist/VanJsfField.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { State } from "vanjs-core";
|
|
2
2
|
import { VanJSComponent } from "./VanJSComponent";
|
|
3
|
+
import { JsfTheme } from "./theme";
|
|
3
4
|
export interface Option {
|
|
4
5
|
label: string;
|
|
5
6
|
value: string;
|
|
@@ -14,15 +15,17 @@ export declare class VanJsfField extends VanJSComponent {
|
|
|
14
15
|
handleChange: (field: VanJsfField, value: MultiType) => void;
|
|
15
16
|
isVisibleState: State<boolean>;
|
|
16
17
|
errorState: State<string>;
|
|
18
|
+
theme: JsfTheme;
|
|
17
19
|
/** Used by file fields to pass file metadata to formValues */
|
|
18
20
|
fileNameValue: string;
|
|
19
21
|
fileSizeValue: string;
|
|
20
22
|
fileTypeValue: string;
|
|
21
|
-
constructor(field: Record<string, unknown>, initVal: MultiType, handleChange: (field: VanJsfField, value: MultiType) => void);
|
|
23
|
+
constructor(field: Record<string, unknown>, initVal: MultiType, handleChange: (field: VanJsfField, value: MultiType) => void, theme?: JsfTheme);
|
|
22
24
|
get inputType(): string;
|
|
23
25
|
get label(): string;
|
|
24
26
|
get class(): string;
|
|
25
27
|
get errorClass(): string;
|
|
28
|
+
get isRequired(): boolean;
|
|
26
29
|
get codemirrorExtension(): Array<any>;
|
|
27
30
|
get containerClass(): string;
|
|
28
31
|
get containerId(): string;
|
|
@@ -34,6 +37,9 @@ export declare class VanJsfField extends VanJSComponent {
|
|
|
34
37
|
set isVisible(val: boolean);
|
|
35
38
|
get error(): string;
|
|
36
39
|
set error(val: string);
|
|
40
|
+
private renderLabel;
|
|
41
|
+
private renderDescription;
|
|
42
|
+
private renderError;
|
|
37
43
|
render(): Element;
|
|
38
44
|
isVanJsfFieldArray(fields: unknown): fields is VanJsfField[];
|
|
39
45
|
}
|
package/dist/VanJsfField.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import van from "vanjs-core";
|
|
2
2
|
import { VanJSComponent } from "./VanJSComponent";
|
|
3
|
+
import { resolve } from "./theme";
|
|
3
4
|
import pikaday from "pikaday";
|
|
4
5
|
import { basicSetup, EditorView } from "codemirror";
|
|
5
6
|
import { javascript, esLint } from "@codemirror/lang-javascript";
|
|
@@ -42,16 +43,18 @@ export class VanJsfField extends VanJSComponent {
|
|
|
42
43
|
handleChange;
|
|
43
44
|
isVisibleState;
|
|
44
45
|
errorState;
|
|
46
|
+
theme;
|
|
45
47
|
/** Used by file fields to pass file metadata to formValues */
|
|
46
48
|
fileNameValue = "";
|
|
47
49
|
fileSizeValue = "";
|
|
48
50
|
fileTypeValue = "";
|
|
49
|
-
constructor(field, initVal, handleChange) {
|
|
51
|
+
constructor(field, initVal, handleChange, theme = {}) {
|
|
50
52
|
super();
|
|
51
53
|
this.field = field;
|
|
52
54
|
this.name = field.name;
|
|
53
55
|
this.iniVal = initVal;
|
|
54
56
|
this.handleChange = handleChange;
|
|
57
|
+
this.theme = theme;
|
|
55
58
|
this.isVisibleState = van.state(this.field.isVisible);
|
|
56
59
|
this.errorState = van.state("");
|
|
57
60
|
}
|
|
@@ -67,9 +70,12 @@ export class VanJsfField extends VanJSComponent {
|
|
|
67
70
|
get errorClass() {
|
|
68
71
|
return this.field.errorClass;
|
|
69
72
|
}
|
|
73
|
+
get isRequired() {
|
|
74
|
+
return this.field.required ?? false;
|
|
75
|
+
}
|
|
70
76
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
71
77
|
get codemirrorExtension() {
|
|
72
|
-
const
|
|
78
|
+
const cmTheme = EditorView.theme({
|
|
73
79
|
'.cm-content, .cm-gutter': {
|
|
74
80
|
"min-height": "150px",
|
|
75
81
|
},
|
|
@@ -86,7 +92,7 @@ export class VanJsfField extends VanJSComponent {
|
|
|
86
92
|
border: '1px solid silver',
|
|
87
93
|
},
|
|
88
94
|
});
|
|
89
|
-
const extensions = [
|
|
95
|
+
const extensions = [cmTheme, EditorView.updateListener.of((e) => {
|
|
90
96
|
this.field.error = null;
|
|
91
97
|
forEachDiagnostic(e.state, (diag) => {
|
|
92
98
|
if (diag.severity === "error") {
|
|
@@ -141,38 +147,52 @@ export class VanJsfField extends VanJSComponent {
|
|
|
141
147
|
set error(val) {
|
|
142
148
|
this.errorState.val = val;
|
|
143
149
|
}
|
|
150
|
+
renderLabel() {
|
|
151
|
+
const cls = resolve(this.titleClass, this.theme.label);
|
|
152
|
+
return label({ for: this.name, class: cls }, this.label, this.isRequired
|
|
153
|
+
? span({ class: this.theme.requiredIndicator || "" }, " *")
|
|
154
|
+
: null);
|
|
155
|
+
}
|
|
156
|
+
renderDescription() {
|
|
157
|
+
if (!this.description)
|
|
158
|
+
return null;
|
|
159
|
+
return div({
|
|
160
|
+
id: `${this.name}-description`,
|
|
161
|
+
class: resolve(this.descriptionClass, this.theme.description),
|
|
162
|
+
}, this.description);
|
|
163
|
+
}
|
|
164
|
+
renderError() {
|
|
165
|
+
return p({ class: resolve(this.errorClass, this.theme.error) }, () => this.error);
|
|
166
|
+
}
|
|
144
167
|
render() {
|
|
145
168
|
let el;
|
|
169
|
+
const baseContainer = resolve(this.containerClass, this.theme.container);
|
|
146
170
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
147
171
|
const props = {
|
|
148
|
-
|
|
149
|
-
class: this.containerClass || ''
|
|
172
|
+
class: () => this.isVisible ? baseContainer : `${baseContainer} jsf-hidden`.trim(),
|
|
150
173
|
};
|
|
151
174
|
switch (this.inputType) {
|
|
152
175
|
case FieldType.text:
|
|
153
|
-
el = div(props,
|
|
154
|
-
div({ id: `${this.name}-description`, class: this.descriptionClass || '' }, this.description), input({
|
|
176
|
+
el = div(props, this.renderLabel(), this.renderDescription(), input({
|
|
155
177
|
id: this.name,
|
|
156
178
|
type: "text",
|
|
157
|
-
class: this.class
|
|
179
|
+
class: resolve(this.class, this.theme.input),
|
|
158
180
|
value: this.iniVal,
|
|
159
181
|
oninput: (e) => this.handleChange(this, e.target.value),
|
|
160
|
-
}),
|
|
182
|
+
}), this.renderError());
|
|
161
183
|
break;
|
|
162
184
|
case FieldType.textarea:
|
|
163
|
-
el = div(props,
|
|
164
|
-
div({ id: `${this.name}-description`, class: this.descriptionClass || '' }, this.description), textarea({
|
|
185
|
+
el = div(props, this.renderLabel(), this.renderDescription(), textarea({
|
|
165
186
|
id: this.name,
|
|
166
187
|
name: this.name,
|
|
167
|
-
class: this.class
|
|
188
|
+
class: resolve(this.class, this.theme.textarea),
|
|
168
189
|
rows: this.field.rows,
|
|
169
190
|
cols: this.field.columns,
|
|
170
191
|
oninput: (e) => this.handleChange(this, e.target.value),
|
|
171
|
-
}),
|
|
192
|
+
}), this.renderError());
|
|
172
193
|
break;
|
|
173
194
|
case FieldType.code:
|
|
174
|
-
el = div(props,
|
|
175
|
-
div({ id: `${this.name}-description`, class: this.descriptionClass || '' }, this.description));
|
|
195
|
+
el = div(props, this.renderLabel(), this.renderDescription());
|
|
176
196
|
new EditorView({
|
|
177
197
|
doc: String(this.iniVal),
|
|
178
198
|
parent: el,
|
|
@@ -180,25 +200,23 @@ export class VanJsfField extends VanJSComponent {
|
|
|
180
200
|
});
|
|
181
201
|
break;
|
|
182
202
|
case FieldType.select:
|
|
183
|
-
el = div(props,
|
|
184
|
-
div({ id: `${this.name}-description`, class: this.descriptionClass || '' }, this.description), select({
|
|
203
|
+
el = div(props, this.renderLabel(), this.renderDescription(), select({
|
|
185
204
|
id: this.name,
|
|
186
205
|
name: this.name,
|
|
187
|
-
class: this.class
|
|
206
|
+
class: resolve(this.class, this.theme.select),
|
|
188
207
|
oninput: (e) => this.handleChange(this, e.target.value),
|
|
189
|
-
}, this.options?.map((opt) => option({ class: this.
|
|
208
|
+
}, this.options?.map((opt) => option({ class: this.theme.option || "", value: opt.value }, opt.label, opt.description))), this.renderError());
|
|
190
209
|
break;
|
|
191
210
|
case FieldType.date: {
|
|
192
211
|
const calendarInput = input({
|
|
193
212
|
id: this.name,
|
|
194
213
|
type: "text",
|
|
195
|
-
class: this.class
|
|
214
|
+
class: resolve(this.class, this.theme.input),
|
|
196
215
|
value: this.iniVal,
|
|
197
216
|
onchange: (e) => this.handleChange(this, e.target.value),
|
|
198
217
|
});
|
|
199
218
|
el =
|
|
200
|
-
div(props,
|
|
201
|
-
div({ id: `${this.name}-description`, class: this.descriptionClass || '' }, this.description), calendarInput, p({ class: this.errorClass }, () => this.error),
|
|
219
|
+
div(props, this.renderLabel(), this.renderDescription(), calendarInput, this.renderError(),
|
|
202
220
|
// External CDN dependency for Pikaday CSS — consider bundling for production
|
|
203
221
|
link({ rel: "stylesheet", type: "text/css", href: "https://cdn.jsdelivr.net/npm/pikaday/css/pikaday.css" }));
|
|
204
222
|
new pikaday({
|
|
@@ -223,31 +241,31 @@ export class VanJsfField extends VanJSComponent {
|
|
|
223
241
|
break;
|
|
224
242
|
}
|
|
225
243
|
case FieldType.number:
|
|
226
|
-
el = div(props,
|
|
227
|
-
div({ id: `${this.name}-description`, class: this.descriptionClass || '' }, this.description), input({
|
|
244
|
+
el = div(props, this.renderLabel(), this.renderDescription(), input({
|
|
228
245
|
id: this.name,
|
|
229
246
|
type: "number",
|
|
230
|
-
class: this.class
|
|
247
|
+
class: resolve(this.class, this.theme.input),
|
|
231
248
|
value: this.iniVal,
|
|
232
249
|
oninput: (e) => {
|
|
233
250
|
const val = e.target.value;
|
|
234
251
|
this.handleChange(this, val === "" ? "" : Number(val));
|
|
235
252
|
},
|
|
236
|
-
}),
|
|
253
|
+
}), this.renderError());
|
|
237
254
|
break;
|
|
238
255
|
case FieldType.fieldset:
|
|
239
|
-
el = div(props, fieldset(legend({ class: this.titleClass ||
|
|
240
|
-
|
|
256
|
+
el = div(props, fieldset({ class: this.theme.fieldset || "" }, legend({ class: resolve(this.titleClass, this.theme.legend || this.theme.label) }, this.label), this.renderDescription(), this.isVanJsfFieldArray(this.field.fields)
|
|
257
|
+
? this.field.fields.map((field) => field.render())
|
|
258
|
+
: null));
|
|
241
259
|
break;
|
|
242
260
|
case FieldType.radio:
|
|
243
|
-
el = div(legend({ class: this.titleClass ||
|
|
261
|
+
el = div(props, legend({ class: resolve(this.titleClass, this.theme.legend || this.theme.label) }, this.label), this.renderDescription(), div({ class: this.theme.radioGroup || "" }, this.options?.map((opt) => label({ class: this.theme.radioLabel || "" }, input({
|
|
244
262
|
type: "radio",
|
|
245
263
|
name: this.name,
|
|
246
|
-
class: this.class
|
|
264
|
+
class: resolve(this.class, this.theme.radioInput),
|
|
247
265
|
value: opt.value,
|
|
248
266
|
checked: this.iniVal === opt.value,
|
|
249
267
|
onchange: (e) => this.handleChange(this, e.target.value),
|
|
250
|
-
}), opt.label, opt.description))),
|
|
268
|
+
}), opt.label, opt.description))), this.renderError());
|
|
251
269
|
break;
|
|
252
270
|
case FieldType.file: {
|
|
253
271
|
const accept = this.field.accept || "";
|
|
@@ -341,11 +359,10 @@ export class VanJsfField extends VanJSComponent {
|
|
|
341
359
|
readFile(files[0]);
|
|
342
360
|
},
|
|
343
361
|
});
|
|
362
|
+
const dzBase = this.theme.dropZone || "jsf-dropzone";
|
|
363
|
+
const dzActive = this.theme.dropZoneActive || "jsf-dropzone-active";
|
|
344
364
|
const dropZone = div({
|
|
345
|
-
|
|
346
|
-
const over = dragOverState.val;
|
|
347
|
-
return `border: 2px dashed ${over ? "#4a90d9" : "#ccc"}; border-radius: 8px; padding: 24px; text-align: center; cursor: pointer; transition: border-color 0.2s; background: ${over ? "#f0f7ff" : "transparent"};`;
|
|
348
|
-
},
|
|
365
|
+
class: () => dragOverState.val ? `${dzBase} ${dzActive}` : dzBase,
|
|
349
366
|
ondragover: (e) => { e.preventDefault(); dragOverState.val = true; },
|
|
350
367
|
ondragleave: () => { dragOverState.val = false; },
|
|
351
368
|
ondrop: (e) => {
|
|
@@ -356,7 +373,7 @@ export class VanJsfField extends VanJSComponent {
|
|
|
356
373
|
readFile(files[0]);
|
|
357
374
|
},
|
|
358
375
|
onclick: () => fileInput.click(),
|
|
359
|
-
}, p({
|
|
376
|
+
}, p({ class: this.theme.dropZoneText || "jsf-dropzone-text" }, accept
|
|
360
377
|
? `Drop a file here or click to browse (${accept})`
|
|
361
378
|
: "Drop a file here or click to browse"));
|
|
362
379
|
const fileInfoBar = () => {
|
|
@@ -364,9 +381,9 @@ export class VanJsfField extends VanJSComponent {
|
|
|
364
381
|
const name = fileNameState.val;
|
|
365
382
|
if (!name)
|
|
366
383
|
return div();
|
|
367
|
-
return div({
|
|
384
|
+
return div({ class: this.theme.fileInfoBar || "jsf-file-info" }, strong({ class: this.theme.fileName || "" }, name), small({ class: this.theme.fileSize || "jsf-file-size" }, `(${fileSizeState.val})`), button({
|
|
368
385
|
type: "button",
|
|
369
|
-
|
|
386
|
+
class: this.theme.fileClearButton || "jsf-file-clear",
|
|
370
387
|
onclick: (e) => {
|
|
371
388
|
e.stopPropagation();
|
|
372
389
|
clearFile();
|
|
@@ -379,11 +396,10 @@ export class VanJsfField extends VanJSComponent {
|
|
|
379
396
|
return div(() => {
|
|
380
397
|
if (!readingState.val)
|
|
381
398
|
return div();
|
|
382
|
-
return div({
|
|
399
|
+
return div({ class: this.theme.fileReading || "jsf-file-reading" }, "Reading file...");
|
|
383
400
|
});
|
|
384
401
|
};
|
|
385
|
-
el = div(props,
|
|
386
|
-
div({ id: `${this.name}-description`, class: this.descriptionClass || '' }, this.description), fileInput, dropZone, fileInfoBar(), readingIndicator(), p({ class: this.errorClass }, () => this.error));
|
|
402
|
+
el = div(props, this.renderLabel(), this.renderDescription(), fileInput, dropZone, fileInfoBar(), readingIndicator(), this.renderError());
|
|
387
403
|
break;
|
|
388
404
|
}
|
|
389
405
|
default:
|
package/dist/VanJsfForm.js
CHANGED
|
@@ -9,7 +9,8 @@ class VanJsfForm {
|
|
|
9
9
|
headlessForm;
|
|
10
10
|
formFields;
|
|
11
11
|
formValues;
|
|
12
|
-
|
|
12
|
+
theme;
|
|
13
|
+
constructor(jsonSchema, config, isValid, theme = {}) {
|
|
13
14
|
// Bind methods to instance. Needed to pass functions as props to child components
|
|
14
15
|
//this.handleSubmit = this.handleSubmit.bind(this);
|
|
15
16
|
this.handleFieldChange = this.handleFieldChange.bind(this);
|
|
@@ -17,6 +18,7 @@ class VanJsfForm {
|
|
|
17
18
|
this.schema = jsonSchema;
|
|
18
19
|
this.config = config;
|
|
19
20
|
this.isValid = isValid || undefined;
|
|
21
|
+
this.theme = theme;
|
|
20
22
|
// Working with parameters
|
|
21
23
|
const initialValues = { ...config?.initialValues };
|
|
22
24
|
this.headlessForm = createHeadlessForm(jsonSchema, config);
|
|
@@ -98,7 +100,7 @@ class VanJsfForm {
|
|
|
98
100
|
field.fields = this.processFields(field.fields, initialValues, formValues, fieldPath);
|
|
99
101
|
}
|
|
100
102
|
// Create and return a new VanJsfField instance for this field
|
|
101
|
-
return new VanJsfField(field, initVal, this.handleFieldChange);
|
|
103
|
+
return new VanJsfField(field, initVal, this.handleFieldChange, this.theme);
|
|
102
104
|
});
|
|
103
105
|
}
|
|
104
106
|
}
|
|
@@ -112,7 +114,8 @@ export function jsform(attributes, ...children) {
|
|
|
112
114
|
if (!config.formValues)
|
|
113
115
|
config.formValues = {};
|
|
114
116
|
const isValid = attributes.isValid;
|
|
115
|
-
const
|
|
117
|
+
const theme = attributes.theme ?? {};
|
|
118
|
+
const vanJsfForm = new VanJsfForm(attributes.schema, config, isValid, theme);
|
|
116
119
|
const fields = vanJsfForm.formFields.map((field) => field.render());
|
|
117
120
|
const childrenWithFields = [...fields, ...children]; // Concatenate fields with other children
|
|
118
121
|
const originalOnSubmit = attributes.onsubmit;
|
package/dist/index.d.ts
CHANGED