vanjs-jsf 0.0.5 → 0.0.8

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.
@@ -2,4 +2,3 @@ import { Fields } from "@remoteoss/json-schema-form";
2
2
  export declare class JsfUtils {
3
3
  static getJsfFieldByName(fields: Fields, name: string): Record<string, unknown> | null;
4
4
  }
5
- //# sourceMappingURL=JsfUtils.d.ts.map
@@ -0,0 +1,10 @@
1
+ export class JsfUtils {
2
+ static getJsfFieldByName(fields, name) {
3
+ for (const field of fields) {
4
+ if (typeof field["name"] === "string" && field["name"] === name) {
5
+ return field;
6
+ }
7
+ }
8
+ return null;
9
+ }
10
+ }
@@ -1,4 +1,3 @@
1
1
  export declare abstract class VanJSComponent {
2
2
  abstract render(): Element;
3
3
  }
4
- //# sourceMappingURL=VanJSComponent.d.ts.map
@@ -0,0 +1,2 @@
1
+ export class VanJSComponent {
2
+ }
@@ -16,6 +16,10 @@ export declare class VanJsfField extends VanJSComponent {
16
16
  constructor(field: Record<string, unknown>, initVal: string, handleChange: (field: VanJsfField, value: MultiType) => void);
17
17
  get inputType(): string;
18
18
  get label(): string;
19
+ get class(): string;
20
+ get containerClass(): string;
21
+ get titleClass(): string;
22
+ get descriptionClass(): string;
19
23
  get description(): string;
20
24
  get options(): Option[];
21
25
  get isVisible(): boolean;
@@ -23,5 +27,5 @@ export declare class VanJsfField extends VanJSComponent {
23
27
  get error(): string;
24
28
  set error(val: string);
25
29
  render(): Element;
30
+ isVanJsfFieldArray(fields: any): fields is VanJsfField[];
26
31
  }
27
- //# sourceMappingURL=VanJsfField.d.ts.map
@@ -0,0 +1,152 @@
1
+ import van from "vanjs-core";
2
+ import { VanJSComponent } from "./VanJSComponent";
3
+ import pikaday from "pikaday";
4
+ const { div, p, input, label, textarea, legend, link, fieldset, span } = van.tags;
5
+ var FieldType;
6
+ (function (FieldType) {
7
+ FieldType["text"] = "text";
8
+ FieldType["number"] = "number";
9
+ FieldType["textarea"] = "textarea";
10
+ FieldType["radio"] = "radio";
11
+ FieldType["date"] = "date";
12
+ FieldType["fieldset"] = "fieldset";
13
+ })(FieldType || (FieldType = {}));
14
+ export class VanJsfField extends VanJSComponent {
15
+ name;
16
+ field;
17
+ iniVal;
18
+ handleChange;
19
+ isVisibleState;
20
+ errorState;
21
+ constructor(field, initVal, handleChange) {
22
+ super();
23
+ this.field = field;
24
+ this.name = field.name;
25
+ this.iniVal = initVal;
26
+ this.handleChange = handleChange;
27
+ this.isVisibleState = van.state(this.field.isVisible);
28
+ this.errorState = van.state("");
29
+ van.derive(() => console.log(`Field ${this.name} isVisible: ${this.isVisibleState.val}`));
30
+ }
31
+ get inputType() {
32
+ return this.field.inputType;
33
+ }
34
+ get label() {
35
+ return this.field.label;
36
+ }
37
+ get class() {
38
+ return this.field.class;
39
+ }
40
+ get containerClass() {
41
+ return this.field.containerClass;
42
+ }
43
+ get titleClass() {
44
+ return this.field.titleClass;
45
+ }
46
+ get descriptionClass() {
47
+ return this.field.descriptionClass;
48
+ }
49
+ get description() {
50
+ return this.field.description;
51
+ }
52
+ get options() {
53
+ return this.field.options;
54
+ }
55
+ get isVisible() {
56
+ return this.isVisibleState.val;
57
+ }
58
+ set isVisible(val) {
59
+ this.isVisibleState.val = val;
60
+ }
61
+ get error() {
62
+ return this.errorState.val;
63
+ }
64
+ set error(val) {
65
+ this.errorState.val = val;
66
+ }
67
+ render() {
68
+ let el;
69
+ const props = {
70
+ style: () => (this.isVisible ? "display: block" : "display: none"),
71
+ class: this.containerClass ? this.containerClass : ''
72
+ };
73
+ switch (this.inputType) {
74
+ case FieldType.text:
75
+ el = div(props, label({ for: this.name, style: "margin-right: 5px;", class: this.titleClass ? this.titleClass : '' }, this.label), this.description &&
76
+ div({ id: `${this.name}-description`, class: this.descriptionClass ? this.descriptionClass : '' }, this.description), input({
77
+ id: this.name,
78
+ type: "text",
79
+ class: this.class ? this.class : '',
80
+ value: this.iniVal,
81
+ oninput: (e) => this.handleChange(this, e.target.value),
82
+ }), p(() => this.error));
83
+ break;
84
+ case FieldType.textarea:
85
+ el = div(props, label({ for: this.name, style: "margin-right: 5px;", class: this.titleClass ? this.titleClass : '' }, this.label), this.description &&
86
+ div({ id: `${this.name}-description`, class: this.descriptionClass ? this.descriptionClass : '' }, this.description), textarea({
87
+ id: this.name,
88
+ name: this.name,
89
+ class: this.class ? this.class : null,
90
+ rows: this.field.rows,
91
+ cols: this.field.columns,
92
+ oninput: (e) => this.handleChange(this, e.target.value),
93
+ }));
94
+ break;
95
+ case FieldType.date:
96
+ const calendarInput = input({
97
+ id: this.name,
98
+ type: "text",
99
+ class: this.class ? this.class : null,
100
+ value: this.iniVal,
101
+ onchange: (e) => this.handleChange(this, e.target.value),
102
+ });
103
+ el =
104
+ div(props, label({ for: this.name, style: "margin-right: 5px;", class: this.titleClass ? this.titleClass : '' }, this.label), this.description &&
105
+ div({ id: `${this.name}-description`, class: this.descriptionClass ? this.descriptionClass : '' }, this.description), calendarInput, link({ rel: "stylesheet", type: "text/css", href: "https://cdn.jsdelivr.net/npm/pikaday/css/pikaday.css" }));
106
+ new pikaday({
107
+ field: calendarInput,
108
+ format: 'D/M/YYYY',
109
+ toString(date, format) {
110
+ // you should do formatting based on the passed format,
111
+ // but we will just return 'D/M/YYYY' for simplicity
112
+ const day = date.getDate();
113
+ const month = date.getMonth() + 1;
114
+ const year = date.getFullYear();
115
+ return `${day}/${month}/${year}`;
116
+ },
117
+ });
118
+ break;
119
+ case FieldType.number:
120
+ el = div(props, label({ for: this.name, style: "margin-right: 5px;", class: this.titleClass ? this.titleClass : '' }, this.label), this.description &&
121
+ div({ id: `${this.name}-description`, class: this.descriptionClass ? this.descriptionClass : '' }, this.description), input({
122
+ id: this.name,
123
+ type: "number",
124
+ class: this.class ? this.class : null,
125
+ value: this.iniVal,
126
+ oninput: (e) => this.handleChange(this, e.target.value),
127
+ }));
128
+ break;
129
+ case FieldType.fieldset:
130
+ console.log(this.field);
131
+ el = div(props, fieldset(legend({ class: this.titleClass ? this.titleClass : '' }, this.label), this.description &&
132
+ span({ id: `${this.name}-description`, class: this.descriptionClass ? this.descriptionClass : '' }, this.description), this.isVanJsfFieldArray(this.field.fields) ? this.field.fields.map((field) => field.render()) : null));
133
+ break;
134
+ case FieldType.radio:
135
+ el = div(legend({ class: this.titleClass ? this.titleClass : '' }, this.label), this.description && div(this.description), div(this.options?.map((opt) => label(input({
136
+ type: "radio",
137
+ name: this.name,
138
+ class: this.class ? this.class : null,
139
+ value: opt.value,
140
+ checked: this.iniVal === opt.value,
141
+ onchange: (e) => this.handleChange(this, e.target.value),
142
+ }), opt.label, opt.description))));
143
+ break;
144
+ default:
145
+ el = div({ style: "border: 1px dashed gray; padding: 8px;" }, `Field "${this.name}" unsupported: The type "${this.inputType}" has no UI component built yet.`);
146
+ }
147
+ return el;
148
+ }
149
+ isVanJsfFieldArray(fields) {
150
+ return Array.isArray(fields) && fields.every(field => field instanceof VanJsfField);
151
+ }
152
+ }
@@ -1,2 +1 @@
1
1
  export declare function jsform(attributes: Record<string, any>, ...children: any[]): HTMLFormElement;
2
- //# sourceMappingURL=VanJsfForm.d.ts.map
@@ -0,0 +1,117 @@
1
+ import van from "vanjs-core";
2
+ import { createHeadlessForm, } from "@remoteoss/json-schema-form";
3
+ import { VanJsfField } from "./VanJsfField";
4
+ const { form } = van.tags;
5
+ class VanJsfForm {
6
+ schema;
7
+ config;
8
+ headlessForm;
9
+ formFields;
10
+ formValues;
11
+ constructor(jsonSchema, config) {
12
+ // Bind methods to instance. Needed to pass functions as props to child components
13
+ //this.handleSubmit = this.handleSubmit.bind(this);
14
+ this.handleFieldChange = this.handleFieldChange.bind(this);
15
+ // Receive parameters
16
+ this.schema = jsonSchema;
17
+ this.config = config;
18
+ // Working with parameters
19
+ this.headlessForm = createHeadlessForm(jsonSchema, config);
20
+ // Read documentation about `getFieldsAndValuedFromJsf` method below
21
+ const { vanJsfFields, formValues } = this.getFieldsAndValuesFromJsf(this.headlessForm, this.config.initialValues);
22
+ this.formFields = vanJsfFields;
23
+ this.formValues = formValues;
24
+ }
25
+ /**
26
+ * Generates fields and their initial values from a headless JSON Schema Form (JSF).
27
+ * This method processes the fields provided by the headless form, maps them to `VanJsfField` instances,
28
+ * and initializes the corresponding form values.
29
+ *
30
+ * @param headlessForm - The output of the `createHeadlessForm` function, containing metadata and configuration for the form fields.
31
+ * @param initialValues - A record object where the keys represent field names, and the values are the initial values for the fields.
32
+ *
33
+ * @returns An object containing:
34
+ * - `vanJsfFields`: An array of `VanJsfField` instances representing the fields in the form.
35
+ * - `formValues`: A record object mapping field names to their respective initial values.
36
+ *
37
+ * @remarks
38
+ * - **Field Sets**: The method currently does not support field sets recursively. This needs to be implemented as part of future enhancements.
39
+ * - **Default Values**:
40
+ * - The default values are determined based on the following precedence:
41
+ * 1. Value in `initialValues`.
42
+ * 2. The `field.default` property.
43
+ * 3. An empty string (`""`) if neither is present.
44
+ * - Note: The `field.default` property is not clearly documented in the JSF API. The documentation mentions `defaultValue` instead, but this is not observed in practice.
45
+ *
46
+ * @example
47
+ * const { vanJsfFields, formValues } = getFieldsFromJsf(headlessForm, initialValues);
48
+ * console.log(vanJsfFields); // Array of VanJsfField instances
49
+ * console.log(formValues); // Record of field names and their initial values
50
+ */
51
+ getFieldsAndValuesFromJsf(headlessForm, initialValues) {
52
+ const fields = headlessForm.fields;
53
+ console.log(fields);
54
+ const formValues = {};
55
+ const vanJsfFields = this.processFields(fields, initialValues, formValues);
56
+ return { vanJsfFields, formValues };
57
+ }
58
+ handleFieldChange(field, value) {
59
+ console.log(`Field ${field.name} changed to ${value}`);
60
+ this.formValues[field.name] = value;
61
+ const { formErrors } = this.headlessForm.handleValidation(this.formValues);
62
+ console.log("formErrors", formErrors);
63
+ this.formFields.forEach((f) => {
64
+ f.isVisible = f.field.isVisible;
65
+ f.error = formErrors?.[f.name] ?? "";
66
+ });
67
+ }
68
+ processFields(fields, initialValues, formValues, parentPath = "") {
69
+ return fields.map((field) => {
70
+ // Construct the full path for the field
71
+ const fieldPath = parentPath ? `${parentPath}.${field.name}` : field.name;
72
+ // Determine the initial value for the field
73
+ const initVal = initialValues[fieldPath] || field.default || "";
74
+ // Store the initial value in the form values map
75
+ formValues[fieldPath] = initVal;
76
+ console.log(formValues);
77
+ console.log(initialValues);
78
+ // Check if the field has nested fields and process them recursively
79
+ if (field.fields && field.fields.length > 0) {
80
+ field.fields = this.processFields(field.fields, initialValues, formValues, fieldPath);
81
+ }
82
+ // Create and return a new VanJsfField instance for this field
83
+ return new VanJsfField(field, initVal, this.handleFieldChange);
84
+ });
85
+ }
86
+ }
87
+ export function jsform(attributes, ...children) {
88
+ if (!attributes.schema) {
89
+ throw new Error("JSON Schema is required");
90
+ }
91
+ let config = attributes.config;
92
+ if (!config) {
93
+ config = { initialValues: {}, formValues: {} };
94
+ }
95
+ else if (!config.initialValues) {
96
+ config.initialValues = {};
97
+ }
98
+ else if (!config.formValues) {
99
+ config.formValues = {};
100
+ }
101
+ const vanJsfForm = new VanJsfForm(attributes.schema, config);
102
+ console.log(vanJsfForm);
103
+ const fields = vanJsfForm.formFields.map((field) => field.render());
104
+ const childrenWithFields = [...fields, ...children]; // Concatenate fields with other children
105
+ const originalOnSubmit = attributes.onsubmit;
106
+ const handleSubmit = (e) => {
107
+ e.preventDefault();
108
+ config.formValues = vanJsfForm.formValues;
109
+ originalOnSubmit && originalOnSubmit(e);
110
+ };
111
+ const handleChange = (e) => {
112
+ config.formValues = vanJsfForm.formValues;
113
+ };
114
+ attributes.onsubmit = handleSubmit;
115
+ attributes.onchange = handleChange;
116
+ return form(attributes, ...childrenWithFields);
117
+ }
package/dist/index.d.ts CHANGED
@@ -1,3 +1 @@
1
- import { jsform } from "./VanJsfForm";
2
- export { jsform };
3
- //# sourceMappingURL=index.d.ts.map
1
+ export { jsform } from "./VanJsfForm";
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export { jsform } from "./VanJsfForm";