@serhiibudianskyi/wui 0.0.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.
@@ -0,0 +1,442 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FieldFactory = exports.Field = void 0;
4
+ const zod_1 = require("zod");
5
+ // Field class representing a form field
6
+ class Field {
7
+ // Constructor to initialize the field with its configuration and schema
8
+ constructor(config, schema) {
9
+ // Configuration for the field
10
+ this._config = {};
11
+ // Zod schema for the field
12
+ this.schema = zod_1.z.any();
13
+ this._config = config;
14
+ this.schema = schema;
15
+ }
16
+ get type() {
17
+ return this._config.type;
18
+ }
19
+ get name() {
20
+ return this._config.name;
21
+ }
22
+ get label() {
23
+ return this._config.label;
24
+ }
25
+ get placeholder() {
26
+ return this._config.placeholder || '';
27
+ }
28
+ get isReadOnly() {
29
+ return this._config.isReadOnly || false;
30
+ }
31
+ get isDisabled() {
32
+ return this._config.isDisabled || false;
33
+ }
34
+ get isRequired() {
35
+ return this._config.isRequired || false;
36
+ }
37
+ get min() {
38
+ return this._config.min;
39
+ }
40
+ get max() {
41
+ return this._config.max;
42
+ }
43
+ get revalidates() {
44
+ return this._config.revalidates || [];
45
+ }
46
+ get step() {
47
+ return this._config.step;
48
+ }
49
+ get options() {
50
+ return this._config.options || [];
51
+ }
52
+ get isMultiple() {
53
+ return this._config.isMultiple || false;
54
+ }
55
+ get maxSize() {
56
+ return this._config.maxSize;
57
+ }
58
+ get accept() {
59
+ return this._config.accept;
60
+ }
61
+ get maxFiles() {
62
+ return this.isMultiple ? this._config.maxFiles || +Infinity : 1;
63
+ }
64
+ get loadOptions() {
65
+ return this._config.loadOptions;
66
+ }
67
+ get className() {
68
+ return this._config.className || '';
69
+ }
70
+ get attrs() {
71
+ return this._config.attrs || {};
72
+ }
73
+ // Normalize the input value based on the field's configuration
74
+ getNormalizedValue(value) {
75
+ return this._config.normalize ? this._config.normalize(value) : value;
76
+ }
77
+ // Run custom validation logic for the field
78
+ runValidation(values, ctx) {
79
+ if (this._config.validate) {
80
+ this._config.validate(values, ctx);
81
+ }
82
+ }
83
+ // Get default value for the field based on its type
84
+ getDefaultValue() {
85
+ var _a;
86
+ switch (this._config.type) {
87
+ case 'file':
88
+ return (this._config.isMultiple ? [] : null);
89
+ case 'number':
90
+ return ((_a = this._config.min) !== null && _a !== void 0 ? _a : 0);
91
+ case 'checkbox':
92
+ return false;
93
+ case 'date':
94
+ case 'datetime-local':
95
+ return new Date();
96
+ case 'time':
97
+ return '00:00';
98
+ case 'select':
99
+ case 'async-select':
100
+ case 'creatable-select':
101
+ case 'async-creatable-select':
102
+ return (this._config.isMultiple ? [] : null);
103
+ case 'textarea':
104
+ case 'text':
105
+ case 'email':
106
+ case 'password':
107
+ case 'radio':
108
+ default:
109
+ return '';
110
+ }
111
+ }
112
+ }
113
+ exports.Field = Field;
114
+ class FieldFactory {
115
+ // Create a Zod schema for string fields
116
+ static createStringSchema(config) {
117
+ let schema = zod_1.z.string();
118
+ // Apply required validation if specified
119
+ if (config.isRequired) {
120
+ schema = schema.nonempty({ message: `${config.label} is required` });
121
+ }
122
+ return schema;
123
+ }
124
+ // Create a Zod schema for email fields
125
+ static createEmailSchema(config) {
126
+ let schema = this.createStringSchema(config).email('Invalid email address');
127
+ return schema;
128
+ }
129
+ // Create a text field with the given configuration
130
+ static text(name, label, config = {}) {
131
+ const fieldConfig = Object.assign({ type: 'text', name,
132
+ label }, config);
133
+ return new Field(fieldConfig, this.createStringSchema(fieldConfig));
134
+ }
135
+ // Create an email field with the given configuration
136
+ static email(config) {
137
+ const fieldConfig = Object.assign({ type: 'email', name: config.name || 'email', label: config.label || 'Email' }, config);
138
+ return new Field(fieldConfig, this.createEmailSchema(fieldConfig));
139
+ }
140
+ // Create a password field with the given configuration
141
+ static password(config) {
142
+ const fieldConfig = Object.assign({ type: 'password', name: config.name || 'password', label: config.label || 'Password' }, config);
143
+ return new Field(fieldConfig, this.createStringSchema(fieldConfig));
144
+ }
145
+ // Create a textarea field with the given configuration
146
+ static textarea(name, label, config = {}) {
147
+ const fieldConfig = Object.assign({ type: 'textarea', name,
148
+ label }, config);
149
+ return new Field(fieldConfig, this.createStringSchema(fieldConfig));
150
+ }
151
+ // Create a Zod schema for number fields
152
+ static createNumberSchema(config) {
153
+ // Preprocess to handle empty strings and convert to number
154
+ let schema = zod_1.z.preprocess((val) => {
155
+ if (typeof val === 'string' && val.trim() === '' || isNaN(Number(val))) {
156
+ return undefined;
157
+ }
158
+ return Number(val);
159
+ }, zod_1.z.number().optional() // It should be optional initially!
160
+ );
161
+ // Apply required and range validations if specified
162
+ if (config.isRequired) {
163
+ schema = schema.refine((val) => val !== undefined, {
164
+ message: `${config.label} is required`
165
+ });
166
+ }
167
+ // Minmax value validation
168
+ schema = schema
169
+ .refine((val) => {
170
+ if (val === undefined)
171
+ return true;
172
+ if (typeof config.min === 'number') {
173
+ return val >= config.min;
174
+ }
175
+ return true;
176
+ }, {
177
+ message: `${config.label} must be at least ${config.min}`
178
+ })
179
+ .refine((val) => {
180
+ if (val === undefined)
181
+ return true;
182
+ if (typeof config.max === 'number') {
183
+ return val <= config.max;
184
+ }
185
+ return true;
186
+ }, {
187
+ message: `${config.label} must be at most ${config.max}`
188
+ });
189
+ return schema;
190
+ }
191
+ // Create a number field with the given configuration
192
+ static number(name, label, config = {}) {
193
+ const fieldConfig = Object.assign({ type: 'number', name,
194
+ label }, config);
195
+ return new Field(fieldConfig, this.createNumberSchema(fieldConfig));
196
+ }
197
+ // Create a Zod schema for checkbox fields
198
+ static createCheckboxSchema(config) {
199
+ let schema = zod_1.z.boolean();
200
+ // Apply required validation if specified
201
+ if (config.isRequired) {
202
+ schema = schema.refine((val) => val === true, {
203
+ message: `${config.label} must be checked`
204
+ });
205
+ }
206
+ return schema;
207
+ }
208
+ // Create a checkbox field with the given configuration
209
+ static checkbox(name, label, config = {}) {
210
+ const fieldConfig = Object.assign({ type: 'checkbox', name,
211
+ label }, config);
212
+ return new Field(fieldConfig, this.createCheckboxSchema(fieldConfig));
213
+ }
214
+ // Create a Zod schema for date fields
215
+ static createDateSchema(config) {
216
+ let schema = zod_1.z.preprocess((val) => {
217
+ if (typeof val === 'string' || val instanceof Date) {
218
+ const date = new Date(val);
219
+ return isNaN(date.getTime()) ? undefined : date;
220
+ }
221
+ return undefined;
222
+ }, zod_1.z.date().optional());
223
+ // Apply required validation if specified
224
+ if (config.isRequired) {
225
+ schema = schema.refine((val) => val !== undefined, {
226
+ message: `${config.label} is required`
227
+ });
228
+ }
229
+ // Minmax date validation
230
+ schema = schema
231
+ .refine((val) => {
232
+ if (val === undefined)
233
+ return true;
234
+ if (typeof config.min === 'string') {
235
+ return val >= new Date(config.min);
236
+ }
237
+ return true;
238
+ }, {
239
+ message: `${config.label} must be on or after ${config.min ? new Date(config.min).toLocaleDateString() : ''}`
240
+ })
241
+ .refine((val) => {
242
+ if (val === undefined)
243
+ return true;
244
+ if (typeof config.max === 'string') {
245
+ return val <= new Date(config.max);
246
+ }
247
+ return true;
248
+ }, {
249
+ message: `${config.label} must be on or before ${config.max ? new Date(config.max).toLocaleDateString() : ''}`
250
+ });
251
+ return schema;
252
+ }
253
+ // Create a date field with the given configuration
254
+ static date(name, label, config = {}) {
255
+ const fieldConfig = Object.assign({ type: 'date', name,
256
+ label }, config);
257
+ return new Field(fieldConfig, this.createDateSchema(fieldConfig));
258
+ }
259
+ // Create a Zod schema for datetime-local fields
260
+ static createDateTimeLocalSchema(config) {
261
+ let schema = zod_1.z.preprocess((val) => {
262
+ if (typeof val === 'string' || val instanceof Date) {
263
+ const date = new Date(val);
264
+ return isNaN(date.getTime()) ? undefined : date;
265
+ }
266
+ return undefined;
267
+ }, zod_1.z.date().optional());
268
+ // Apply required validation if specified
269
+ if (config.isRequired) {
270
+ schema = schema.refine((val) => val !== undefined, {
271
+ message: `${config.label} is required`
272
+ });
273
+ }
274
+ // Minmax datetime validation
275
+ schema = schema
276
+ .refine((val) => {
277
+ if (val === undefined)
278
+ return true;
279
+ if (typeof config.min === 'string') {
280
+ return val >= new Date(config.min);
281
+ }
282
+ return true;
283
+ }, {
284
+ message: `${config.label} must be on or after ${config.min ? new Date(config.min).toLocaleString() : ''}`
285
+ })
286
+ .refine((val) => {
287
+ if (val === undefined)
288
+ return true;
289
+ if (typeof config.max === 'string') {
290
+ return val <= new Date(config.max);
291
+ }
292
+ return true;
293
+ }, {
294
+ message: `${config.label} must be on or before ${config.max ? new Date(config.max).toLocaleString() : ''}`
295
+ });
296
+ return schema;
297
+ }
298
+ // Create a datetime-local field with the given configuration
299
+ static datetimeLocal(name, label, config = {}) {
300
+ const fieldConfig = Object.assign({ type: 'datetime-local', name,
301
+ label }, config);
302
+ return new Field(fieldConfig, this.createDateTimeLocalSchema(fieldConfig));
303
+ }
304
+ // Create a Zod schema for time fields
305
+ static createTimeSchema(config) {
306
+ let schema = zod_1.z.string().optional();
307
+ // Apply required validation if specified
308
+ if (config.isRequired) {
309
+ schema = schema.refine((val) => val !== undefined && val !== '', {
310
+ message: `${config.label} is required`
311
+ });
312
+ }
313
+ // Validate time format (HH:MM)
314
+ schema = schema.refine((val) => !val || /^([01]\d|2[0-3]):([0-5]\d)$/.test(val), {
315
+ message: `${config.label} must be a valid time in HH:MM format`
316
+ });
317
+ // Minmax time validation
318
+ schema = schema
319
+ .refine((val) => {
320
+ if (!val)
321
+ return true;
322
+ if (typeof config.min === 'string') {
323
+ return val >= config.min;
324
+ }
325
+ return true;
326
+ }, {
327
+ message: `${config.label} must be at or after ${config.min}`
328
+ })
329
+ .refine((val) => {
330
+ if (!val)
331
+ return true;
332
+ if (typeof config.max === 'string') {
333
+ return val <= config.max;
334
+ }
335
+ return true;
336
+ }, {
337
+ message: `${config.label} must be at or before ${config.max}`
338
+ });
339
+ return schema;
340
+ }
341
+ // Create a time field with the given configuration
342
+ static time(name, label, config = {}) {
343
+ const fieldConfig = Object.assign({ type: 'time', name,
344
+ label }, config);
345
+ return new Field(fieldConfig, this.createTimeSchema(fieldConfig));
346
+ }
347
+ // Create a radio field with the given configuration
348
+ static radio(name, label, options, config = {}) {
349
+ const fieldConfig = Object.assign({ type: 'radio', name,
350
+ label,
351
+ options }, config);
352
+ return new Field(fieldConfig, this.createStringSchema(fieldConfig));
353
+ }
354
+ // Create a Zod schema for select fields
355
+ static createSelectSchema(config) {
356
+ if (config.isMultiple) {
357
+ // Array schema for multi-select
358
+ let schema = zod_1.z.array(zod_1.z.object({
359
+ label: zod_1.z.string(),
360
+ value: zod_1.z.string()
361
+ }));
362
+ // Apply required validation
363
+ if (config.isRequired) {
364
+ schema = schema.nonempty({ message: `${config.label} is required` });
365
+ }
366
+ return schema.default([]);
367
+ }
368
+ else {
369
+ // For single-select, return an Option object or null
370
+ let schema = zod_1.z.object({
371
+ label: zod_1.z.string(),
372
+ value: zod_1.z.string()
373
+ }).nullable().default(null);
374
+ // Apply required validation
375
+ if (config.isRequired) {
376
+ schema = schema.refine((val) => val !== null, { message: `${config.label} is required` });
377
+ }
378
+ return schema;
379
+ }
380
+ }
381
+ // Create a select field with the given configuration
382
+ static select(name, label, options, config = {}) {
383
+ const fieldConfig = Object.assign({ type: 'select', name,
384
+ label,
385
+ options }, config);
386
+ return new Field(fieldConfig, this.createSelectSchema(fieldConfig));
387
+ }
388
+ // Create an async-select field with the given configuration
389
+ static asyncSelect(name, label, loadOptions, config = {}) {
390
+ const fieldConfig = Object.assign({ type: 'async-select', name,
391
+ label,
392
+ loadOptions }, config);
393
+ return new Field(fieldConfig, this.createSelectSchema(fieldConfig));
394
+ }
395
+ // Create a creatable-select field with the given configuration
396
+ static creatableSelect(name, label, options, config = {}) {
397
+ const fieldConfig = Object.assign({ type: 'creatable-select', name,
398
+ label,
399
+ options }, config);
400
+ return new Field(fieldConfig, this.createSelectSchema(fieldConfig));
401
+ }
402
+ // Create an async-creatable-select field with the given configuration
403
+ static asyncCreatableSelect(name, label, loadOptions, config = {}) {
404
+ const fieldConfig = Object.assign({ type: 'async-creatable-select', name,
405
+ label,
406
+ loadOptions }, config);
407
+ return new Field(fieldConfig, this.createSelectSchema(fieldConfig));
408
+ }
409
+ static createFileSchema(config) {
410
+ if (config.isMultiple) {
411
+ // Array schema for multiple files
412
+ let schema = zod_1.z.array(zod_1.z.string());
413
+ // Apply required validation
414
+ if (config.isRequired) {
415
+ schema = schema.nonempty({ message: `${config.label} is required` });
416
+ }
417
+ // Apply maxFiles validation
418
+ if (config.maxFiles) {
419
+ schema = schema.max(config.maxFiles, {
420
+ message: `${config.label} cannot have more than ${config.maxFiles} file${config.maxFiles > 1 ? 's' : ''}`
421
+ });
422
+ }
423
+ return schema.default([]);
424
+ }
425
+ else {
426
+ // For single file, return a string (file ID) or null
427
+ let schema = zod_1.z.string().nullable().default(null);
428
+ // Apply required validation
429
+ if (config.isRequired) {
430
+ schema = schema.refine((val) => val !== null, { message: `${config.label} is required` });
431
+ }
432
+ return schema;
433
+ }
434
+ }
435
+ // Create a file field with the given configuration
436
+ static file(name, label, config = {}) {
437
+ const fieldConfig = Object.assign({ type: 'file', name,
438
+ label }, config);
439
+ return new Field(fieldConfig, this.createFileSchema(fieldConfig));
440
+ }
441
+ }
442
+ exports.FieldFactory = FieldFactory;
@@ -0,0 +1,29 @@
1
+ import { z } from 'zod';
2
+ import { Field } from './Field';
3
+ export interface Section {
4
+ title?: string;
5
+ fields: Record<string, Field<any>>;
6
+ className?: string;
7
+ }
8
+ export declare class Form {
9
+ private _fields;
10
+ private _schema;
11
+ private _defaultValues;
12
+ private _sections;
13
+ constructor(sections: Section[]);
14
+ get sections(): Section[];
15
+ get schema(): z.ZodObject<any>;
16
+ get defaultValues(): Record<string, any>;
17
+ setDefaultValues(values: Record<string, any>): void;
18
+ private _buildSchema;
19
+ private _buildDefaultValues;
20
+ private _extractFieldsFromSections;
21
+ }
22
+ export declare class FormBuilder {
23
+ private _sections;
24
+ private _defaultValues;
25
+ section(section: Section): this;
26
+ sections(sections: Section[]): this;
27
+ defaultValues(values: Record<string, any>): this;
28
+ build(): Form;
29
+ }
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FormBuilder = exports.Form = void 0;
4
+ const zod_1 = require("zod");
5
+ class Form {
6
+ // Alternative constructor to initialize the form with sections
7
+ constructor(sections) {
8
+ // Form fields
9
+ this._fields = {};
10
+ // Zod schema for the form
11
+ this._schema = null;
12
+ // Default values for the form
13
+ this._defaultValues = {};
14
+ // Sections of the form
15
+ this._sections = [];
16
+ this._sections = sections;
17
+ this._fields = this._extractFieldsFromSections();
18
+ this._buildDefaultValues();
19
+ }
20
+ get sections() {
21
+ return this._sections;
22
+ }
23
+ get schema() {
24
+ // Build the schema if it hasn't been built yet
25
+ if (!this._schema) {
26
+ this._schema = this._buildSchema();
27
+ }
28
+ return this._schema;
29
+ }
30
+ // Get default values for the form
31
+ get defaultValues() {
32
+ return this._defaultValues;
33
+ }
34
+ // Set default values for the form
35
+ setDefaultValues(values) {
36
+ this._defaultValues = Object.assign(Object.assign({}, this._defaultValues), values);
37
+ }
38
+ // Build the Zod schema for the form based on its fields
39
+ _buildSchema() {
40
+ const shape = {};
41
+ // Iterate over each field to build its schema
42
+ for (const [name, field] of Object.entries(this._fields)) {
43
+ let schema = field.schema;
44
+ // Wrap the schema with preprocessing to normalize the value
45
+ schema = zod_1.z.preprocess(field.getNormalizedValue.bind(field), schema);
46
+ shape[name] = schema;
47
+ }
48
+ // Return the combined schema with custom refinements callback
49
+ return zod_1.z.object(shape).superRefine((values, ctx) => {
50
+ for (const field of Object.values(this._fields)) {
51
+ field.runValidation(values, ctx);
52
+ }
53
+ });
54
+ }
55
+ // Build default values for the form based on its fields
56
+ _buildDefaultValues() {
57
+ this._defaultValues = {};
58
+ for (const [key, field] of Object.entries(this._fields)) {
59
+ this._defaultValues[key] = field.getDefaultValue();
60
+ }
61
+ }
62
+ // Extract fields from sections
63
+ _extractFieldsFromSections() {
64
+ const fields = {};
65
+ for (const section of this._sections) {
66
+ Object.assign(fields, section.fields);
67
+ }
68
+ return fields;
69
+ }
70
+ }
71
+ exports.Form = Form;
72
+ class FormBuilder {
73
+ constructor() {
74
+ // Set of sections of fields for the form
75
+ this._sections = [];
76
+ // Default values for the form
77
+ this._defaultValues = {};
78
+ }
79
+ // Add a section to the form
80
+ section(section) {
81
+ this._sections.push(section);
82
+ // Extract default values from section fields
83
+ for (const [name, field] of Object.entries(section.fields)) {
84
+ this._defaultValues[name] = field.getDefaultValue();
85
+ }
86
+ return this;
87
+ }
88
+ // Add multiple sections to the form
89
+ sections(sections) {
90
+ for (const section of sections) {
91
+ this.section(section);
92
+ }
93
+ return this;
94
+ }
95
+ // Set default values for the form
96
+ defaultValues(values) {
97
+ this._defaultValues = Object.assign(Object.assign({}, this._defaultValues), values);
98
+ return this;
99
+ }
100
+ // Build and return the form instance
101
+ build() {
102
+ const form = new Form(this._sections);
103
+ form.setDefaultValues(this._defaultValues);
104
+ return form;
105
+ }
106
+ }
107
+ exports.FormBuilder = FormBuilder;
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@serhiibudianskyi/wui",
3
+ "version": "0.0.1",
4
+ "description": "Web User Interface npm libray",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc -p tsconfig.json",
9
+ "prepublishOnly": "npm run build"
10
+ },
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "keywords": [
15
+ "form",
16
+ "fields",
17
+ "library",
18
+ "typescript"
19
+ ],
20
+ "author": "Serhii Budianskyi",
21
+ "license": "MIT",
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://github.com/serhiibudianskyi/wui.git"
25
+ },
26
+ "homepage": "https://github.com/serhiibudianskyi/wui#readme",
27
+ "bugs": {
28
+ "url": "https://github.com/serhiibudianskyi/wui/issues"
29
+ },
30
+ "engines": {
31
+ "node": ">=16.0.0"
32
+ },
33
+ "peerDependencies": {
34
+ "typescript": "^5.0.0",
35
+ "react": "^18.2.0",
36
+ "react-dom": "^18.2.0",
37
+ "react-hook-form": "^7.53.0",
38
+ "react-router-dom": "^7.6.3",
39
+ "react-select": "^5.10.2",
40
+ "react-toastify": "^10.0.6",
41
+ "@types/react": "^18.2.0",
42
+ "@types/react-dom": "^18.2.0"
43
+ },
44
+ "dependencies": {
45
+ "zod": "^4.0.17",
46
+ "filesize": "^11.0.13",
47
+ "@rpldy/uploady": "^1.12.0",
48
+ "@rpldy/upload-button": "^1.12.0",
49
+ "@rpldy/upload-preview": "^1.12.0",
50
+ "@rpldy/upload-drop-zone": "^1.12.0",
51
+ "@hookform/resolvers": "^5.2.1"
52
+ },
53
+ "exclude": [
54
+ "node_modules",
55
+ "dist"
56
+ ]
57
+ }