@vendure/dashboard 3.4.1-master-202508050244 → 3.4.1-master-202508070243
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/package.json +4 -4
- package/src/lib/components/data-input/money-input.tsx +1 -0
- package/src/lib/components/data-input/rich-text-input.tsx +21 -7
- package/src/lib/components/shared/asset/asset-gallery.tsx +9 -2
- package/src/lib/components/shared/asset/asset-picker-dialog.tsx +1 -0
- package/src/lib/components/shared/configurable-operation-arg-input.tsx +13 -372
- package/src/lib/components/shared/custom-fields-form.tsx +19 -143
- package/src/lib/components/shared/direct-form-component-map.tsx +393 -0
- package/src/lib/components/shared/universal-field-definition.ts +118 -0
- package/src/lib/components/shared/universal-form-input.tsx +175 -0
- package/src/lib/components/shared/universal-input-components.tsx +291 -0
- package/src/lib/components/shared/value-transformers.ts +143 -0
- package/src/lib/framework/form-engine/form-schema-tools.spec.ts +138 -0
- package/src/lib/framework/form-engine/form-schema-tools.ts +4 -4
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { UniversalFieldDefinition } from './universal-field-definition.js';
|
|
2
|
+
|
|
3
|
+
export type ValueMode = 'native' | 'json-string';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Interface for transforming values between native JavaScript types and JSON strings
|
|
7
|
+
*/
|
|
8
|
+
export interface ValueTransformer {
|
|
9
|
+
parse: (value: string, fieldDef: UniversalFieldDefinition) => any;
|
|
10
|
+
serialize: (value: any, fieldDef: UniversalFieldDefinition) => string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Native value transformer - passes values through unchanged
|
|
15
|
+
*/
|
|
16
|
+
export const nativeValueTransformer: ValueTransformer = {
|
|
17
|
+
parse: (value: string, fieldDef: UniversalFieldDefinition) => {
|
|
18
|
+
// For native mode, values are already in their correct JavaScript type
|
|
19
|
+
return value;
|
|
20
|
+
},
|
|
21
|
+
serialize: (value: any, fieldDef: UniversalFieldDefinition) => {
|
|
22
|
+
// For native mode, values are already in their correct JavaScript type
|
|
23
|
+
return value;
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* JSON string value transformer - converts between JSON strings and native values
|
|
29
|
+
*/
|
|
30
|
+
export const jsonStringValueTransformer: ValueTransformer = {
|
|
31
|
+
parse: (value: string, fieldDef: UniversalFieldDefinition) => {
|
|
32
|
+
if (!value) {
|
|
33
|
+
return getDefaultValue(fieldDef);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
// For JSON string mode, parse the string to get the native value
|
|
38
|
+
const parsed = JSON.parse(value);
|
|
39
|
+
|
|
40
|
+
// Handle special cases for different field types
|
|
41
|
+
switch (fieldDef.type) {
|
|
42
|
+
case 'boolean':
|
|
43
|
+
return parsed === true || parsed === 'true';
|
|
44
|
+
case 'int':
|
|
45
|
+
case 'float':
|
|
46
|
+
return typeof parsed === 'number' ? parsed : parseFloat(parsed) || 0;
|
|
47
|
+
case 'datetime':
|
|
48
|
+
return parsed;
|
|
49
|
+
default:
|
|
50
|
+
return parsed;
|
|
51
|
+
}
|
|
52
|
+
} catch (error) {
|
|
53
|
+
// If parsing fails, try to handle as a plain string for certain types
|
|
54
|
+
switch (fieldDef.type) {
|
|
55
|
+
case 'boolean':
|
|
56
|
+
return value === 'true';
|
|
57
|
+
case 'int':
|
|
58
|
+
case 'float':
|
|
59
|
+
return parseFloat(value) || 0;
|
|
60
|
+
default:
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
serialize: (value: any, fieldDef: UniversalFieldDefinition) => {
|
|
66
|
+
if (value === null || value === undefined) {
|
|
67
|
+
return '';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Handle special cases for different field types
|
|
71
|
+
switch (fieldDef.type) {
|
|
72
|
+
case 'boolean':
|
|
73
|
+
return (value === true || value === 'true').toString();
|
|
74
|
+
case 'int':
|
|
75
|
+
case 'float':
|
|
76
|
+
return typeof value === 'number' ? value.toString() : (parseFloat(value) || 0).toString();
|
|
77
|
+
case 'string':
|
|
78
|
+
return typeof value === 'string' ? value : JSON.stringify(value);
|
|
79
|
+
default:
|
|
80
|
+
// For complex values (arrays, objects), serialize as JSON
|
|
81
|
+
return typeof value === 'string' ? value : JSON.stringify(value);
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Get the appropriate value transformer based on the value mode
|
|
88
|
+
*/
|
|
89
|
+
export function getValueTransformer(valueMode: ValueMode): ValueTransformer {
|
|
90
|
+
switch (valueMode) {
|
|
91
|
+
case 'native':
|
|
92
|
+
return nativeValueTransformer;
|
|
93
|
+
case 'json-string':
|
|
94
|
+
return jsonStringValueTransformer;
|
|
95
|
+
default:
|
|
96
|
+
return nativeValueTransformer;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Get default value for a field type
|
|
102
|
+
*/
|
|
103
|
+
function getDefaultValue(fieldDef: UniversalFieldDefinition): any {
|
|
104
|
+
if (fieldDef.list) {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
switch (fieldDef.type) {
|
|
109
|
+
case 'string':
|
|
110
|
+
case 'ID':
|
|
111
|
+
case 'localeString':
|
|
112
|
+
case 'localeText':
|
|
113
|
+
return '';
|
|
114
|
+
case 'int':
|
|
115
|
+
case 'float':
|
|
116
|
+
return 0;
|
|
117
|
+
case 'boolean':
|
|
118
|
+
return false;
|
|
119
|
+
case 'datetime':
|
|
120
|
+
return '';
|
|
121
|
+
case 'relation':
|
|
122
|
+
return fieldDef.list ? [] : null;
|
|
123
|
+
case 'struct':
|
|
124
|
+
return fieldDef.list ? [] : {};
|
|
125
|
+
default:
|
|
126
|
+
return '';
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Utility to transform a value using the appropriate transformer
|
|
132
|
+
*/
|
|
133
|
+
export function transformValue(
|
|
134
|
+
value: any,
|
|
135
|
+
fieldDef: UniversalFieldDefinition,
|
|
136
|
+
valueMode: ValueMode,
|
|
137
|
+
direction: 'parse' | 'serialize',
|
|
138
|
+
): any {
|
|
139
|
+
const transformer = getValueTransformer(valueMode);
|
|
140
|
+
return direction === 'parse'
|
|
141
|
+
? transformer.parse(value, fieldDef)
|
|
142
|
+
: transformer.serialize(value, fieldDef);
|
|
143
|
+
}
|
|
@@ -434,6 +434,144 @@ describe('form-schema-tools', () => {
|
|
|
434
434
|
});
|
|
435
435
|
});
|
|
436
436
|
|
|
437
|
+
describe('createFormSchemaFromFields - null constraint handling', () => {
|
|
438
|
+
it('should handle int custom fields with null min constraint from GraphQL API', () => {
|
|
439
|
+
const fields = [createMockField('customFields', 'Object', false, false, [])];
|
|
440
|
+
// Simulate GraphQL API response where min/max are null instead of undefined
|
|
441
|
+
const customFields = [
|
|
442
|
+
createMockCustomField('quantity', 'int', {
|
|
443
|
+
intMin: null as any, // This comes as null from GraphQL API
|
|
444
|
+
intMax: 100,
|
|
445
|
+
}),
|
|
446
|
+
];
|
|
447
|
+
|
|
448
|
+
// This test should fail if the bug exists - the schema creation should work
|
|
449
|
+
// but validation with null constraints should fail
|
|
450
|
+
expect(() => {
|
|
451
|
+
const _schema = createFormSchemaFromFields(fields, customFields, false);
|
|
452
|
+
|
|
453
|
+
// If the bug exists, this will try to validate against null min value
|
|
454
|
+
// which would always be false since no number can be >= null
|
|
455
|
+
const _validData = { customFields: { quantity: 50 } };
|
|
456
|
+
_schema.parse(_validData);
|
|
457
|
+
}).not.toThrow();
|
|
458
|
+
|
|
459
|
+
// Let's also test that we can create the schema without errors
|
|
460
|
+
const schema = createFormSchemaFromFields(fields, customFields, false);
|
|
461
|
+
|
|
462
|
+
// Should not apply min validation since intMin is null (if bug is fixed)
|
|
463
|
+
const validData = { customFields: { quantity: -50 } };
|
|
464
|
+
expect(() => schema.parse(validData)).not.toThrow();
|
|
465
|
+
|
|
466
|
+
// Should still apply max validation since intMax is 100
|
|
467
|
+
const aboveMaxData = { customFields: { quantity: 101 } };
|
|
468
|
+
expect(() => schema.parse(aboveMaxData)).toThrow();
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
it('should handle int custom fields with null max constraint from GraphQL API', () => {
|
|
472
|
+
const fields = [createMockField('customFields', 'Object', false, false, [])];
|
|
473
|
+
// Simulate GraphQL API response where min/max are null instead of undefined
|
|
474
|
+
const customFields = [
|
|
475
|
+
createMockCustomField('quantity', 'int', {
|
|
476
|
+
intMin: 1,
|
|
477
|
+
intMax: null as any, // This comes as null from GraphQL API
|
|
478
|
+
}),
|
|
479
|
+
];
|
|
480
|
+
|
|
481
|
+
const schema = createFormSchemaFromFields(fields, customFields, false);
|
|
482
|
+
|
|
483
|
+
// Should still apply min validation since intMin is 1
|
|
484
|
+
const belowMinData = { customFields: { quantity: 0 } };
|
|
485
|
+
expect(() => schema.parse(belowMinData)).toThrow();
|
|
486
|
+
|
|
487
|
+
// Should not apply max validation since intMax is null
|
|
488
|
+
const validData = { customFields: { quantity: 999999 } };
|
|
489
|
+
expect(() => schema.parse(validData)).not.toThrow();
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
it('should handle int custom fields with both null min and max constraints from GraphQL API', () => {
|
|
493
|
+
const fields = [createMockField('customFields', 'Object', false, false, [])];
|
|
494
|
+
// Simulate GraphQL API response where min/max are both null
|
|
495
|
+
const customFields = [
|
|
496
|
+
createMockCustomField('quantity', 'int', {
|
|
497
|
+
intMin: null as any, // This comes as null from GraphQL API
|
|
498
|
+
intMax: null as any, // This comes as null from GraphQL API
|
|
499
|
+
}),
|
|
500
|
+
];
|
|
501
|
+
|
|
502
|
+
const schema = createFormSchemaFromFields(fields, customFields, false);
|
|
503
|
+
|
|
504
|
+
// Should not apply any validation since both are null
|
|
505
|
+
const negativeData = { customFields: { quantity: -999 } };
|
|
506
|
+
expect(() => schema.parse(negativeData)).not.toThrow();
|
|
507
|
+
|
|
508
|
+
const largeData = { customFields: { quantity: 999999 } };
|
|
509
|
+
expect(() => schema.parse(largeData)).not.toThrow();
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
it('should handle float custom fields with null min constraint from GraphQL API', () => {
|
|
513
|
+
const fields = [createMockField('customFields', 'Object', false, false, [])];
|
|
514
|
+
// Simulate GraphQL API response where min/max are null instead of undefined
|
|
515
|
+
const customFields = [
|
|
516
|
+
createMockCustomField('weight', 'float', {
|
|
517
|
+
floatMin: null as any, // This comes as null from GraphQL API
|
|
518
|
+
floatMax: 999.9,
|
|
519
|
+
}),
|
|
520
|
+
];
|
|
521
|
+
|
|
522
|
+
const schema = createFormSchemaFromFields(fields, customFields, false);
|
|
523
|
+
|
|
524
|
+
// Should not apply min validation since floatMin is null
|
|
525
|
+
const validData = { customFields: { weight: -50.5 } };
|
|
526
|
+
expect(() => schema.parse(validData)).not.toThrow();
|
|
527
|
+
|
|
528
|
+
// Should still apply max validation since floatMax is 999.9
|
|
529
|
+
const aboveMaxData = { customFields: { weight: 1000.0 } };
|
|
530
|
+
expect(() => schema.parse(aboveMaxData)).toThrow();
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
it('should handle float custom fields with null max constraint from GraphQL API', () => {
|
|
534
|
+
const fields = [createMockField('customFields', 'Object', false, false, [])];
|
|
535
|
+
// Simulate GraphQL API response where min/max are null instead of undefined
|
|
536
|
+
const customFields = [
|
|
537
|
+
createMockCustomField('weight', 'float', {
|
|
538
|
+
floatMin: 0.1,
|
|
539
|
+
floatMax: null as any, // This comes as null from GraphQL API
|
|
540
|
+
}),
|
|
541
|
+
];
|
|
542
|
+
|
|
543
|
+
const schema = createFormSchemaFromFields(fields, customFields, false);
|
|
544
|
+
|
|
545
|
+
// Should still apply min validation since floatMin is 0.1
|
|
546
|
+
const belowMinData = { customFields: { weight: 0.05 } };
|
|
547
|
+
expect(() => schema.parse(belowMinData)).toThrow();
|
|
548
|
+
|
|
549
|
+
// Should not apply max validation since floatMax is null
|
|
550
|
+
const validData = { customFields: { weight: 999999.99 } };
|
|
551
|
+
expect(() => schema.parse(validData)).not.toThrow();
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
it('should handle float custom fields with both null min and max constraints from GraphQL API', () => {
|
|
555
|
+
const fields = [createMockField('customFields', 'Object', false, false, [])];
|
|
556
|
+
// Simulate GraphQL API response where min/max are both null
|
|
557
|
+
const customFields = [
|
|
558
|
+
createMockCustomField('weight', 'float', {
|
|
559
|
+
floatMin: null as any, // This comes as null from GraphQL API
|
|
560
|
+
floatMax: null as any, // This comes as null from GraphQL API
|
|
561
|
+
}),
|
|
562
|
+
];
|
|
563
|
+
|
|
564
|
+
const schema = createFormSchemaFromFields(fields, customFields, false);
|
|
565
|
+
|
|
566
|
+
// Should not apply any validation since both are null
|
|
567
|
+
const negativeData = { customFields: { weight: -999.99 } };
|
|
568
|
+
expect(() => schema.parse(negativeData)).not.toThrow();
|
|
569
|
+
|
|
570
|
+
const largeData = { customFields: { weight: 999999.99 } };
|
|
571
|
+
expect(() => schema.parse(largeData)).not.toThrow();
|
|
572
|
+
});
|
|
573
|
+
});
|
|
574
|
+
|
|
437
575
|
describe('createFormSchemaFromFields - edge cases', () => {
|
|
438
576
|
it('should handle empty custom field config', () => {
|
|
439
577
|
const fields = [
|
|
@@ -136,12 +136,12 @@ function createStringValidationSchema(pattern?: string): ZodType {
|
|
|
136
136
|
*/
|
|
137
137
|
function createIntValidationSchema(min?: number, max?: number): ZodType {
|
|
138
138
|
let schema = z.number();
|
|
139
|
-
if (min
|
|
139
|
+
if (min != null) {
|
|
140
140
|
schema = schema.min(min, {
|
|
141
141
|
message: `Value must be at least ${min}`,
|
|
142
142
|
});
|
|
143
143
|
}
|
|
144
|
-
if (max
|
|
144
|
+
if (max != null) {
|
|
145
145
|
schema = schema.max(max, {
|
|
146
146
|
message: `Value must be at most ${max}`,
|
|
147
147
|
});
|
|
@@ -159,12 +159,12 @@ function createIntValidationSchema(min?: number, max?: number): ZodType {
|
|
|
159
159
|
*/
|
|
160
160
|
function createFloatValidationSchema(min?: number, max?: number): ZodType {
|
|
161
161
|
let schema = z.number();
|
|
162
|
-
if (min
|
|
162
|
+
if (min != null) {
|
|
163
163
|
schema = schema.min(min, {
|
|
164
164
|
message: `Value must be at least ${min}`,
|
|
165
165
|
});
|
|
166
166
|
}
|
|
167
|
-
if (max
|
|
167
|
+
if (max != null) {
|
|
168
168
|
schema = schema.max(max, {
|
|
169
169
|
message: `Value must be at most ${max}`,
|
|
170
170
|
});
|