@datenlotse/jsonjoy-builder 0.4.0 → 0.5.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.
@@ -2,9 +2,10 @@ import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { ChevronDown, ChevronRight, ChevronUp, X } from "lucide-react";
3
3
  import { useEffect, useMemo, useState } from "react";
4
4
  import { Input } from "../ui/input.js";
5
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select.js";
5
6
  import { useTranslation } from "../../hooks/use-translation.js";
6
7
  import { cn } from "../../lib/utils.js";
7
- import { asObjectSchema, getSchemaDescription, withObjectSchema } from "../../types/jsonSchema.js";
8
+ import { asObjectSchema, getSchemaDescription, isBooleanSchema, withObjectSchema } from "../../types/jsonSchema.js";
8
9
  import { Badge } from "../ui/badge.js";
9
10
  import TypeDropdown from "./TypeDropdown.js";
10
11
  import TypeEditor from "./TypeEditor.js";
@@ -23,6 +24,33 @@ const SchemaPropertyEditor = ({ name, schema, required, readOnly = false, parent
23
24
  schema
24
25
  ]);
25
26
  const [tempDefault, setTempDefault] = useState(defaultValue);
27
+ const defaultValueOptions = useMemo(()=>{
28
+ if ("string" !== type && "number" !== type && "integer" !== type) return null;
29
+ const objSchema = asObjectSchema(schema);
30
+ const enumValues = objSchema.enum;
31
+ const dependentEnum = objSchema.$dependentEnum;
32
+ if (Array.isArray(enumValues) && enumValues.length > 0) return enumValues.map((v)=>String(v));
33
+ if (dependentEnum && parentSchema) {
34
+ const controllingPropertyName = dependentEnum.property;
35
+ const controllingPropertySchema = parentSchema.properties?.[controllingPropertyName];
36
+ if (controllingPropertySchema && "object" == typeof controllingPropertySchema && !isBooleanSchema(controllingPropertySchema)) {
37
+ const controllingDefault = controllingPropertySchema.default;
38
+ if (void 0 !== controllingDefault) {
39
+ const ctrlDefaultStr = String(controllingDefault);
40
+ const allowedValues = dependentEnum.values[ctrlDefaultStr];
41
+ if (Array.isArray(allowedValues) && allowedValues.length > 0) return allowedValues.map((v)=>String(v));
42
+ }
43
+ }
44
+ }
45
+ return null;
46
+ }, [
47
+ schema,
48
+ type,
49
+ parentSchema
50
+ ]);
51
+ const defaultError = useMemo(()=>validationNode?.validation.errors?.find((err)=>"default" === err.path[0])?.message, [
52
+ validationNode
53
+ ]);
26
54
  useEffect(()=>{
27
55
  setTempName(name);
28
56
  setTempDesc(getSchemaDescription(schema));
@@ -78,6 +106,26 @@ const SchemaPropertyEditor = ({ name, schema, required, readOnly = false, parent
78
106
  });
79
107
  }
80
108
  };
109
+ const handleDefaultSelectChange = (value)=>{
110
+ const objSchema = asObjectSchema(schema);
111
+ const currentDefault = objSchema.default;
112
+ if ("__none__" === value || "" === value) {
113
+ if (void 0 !== currentDefault) {
114
+ const { default: _, ...rest } = objSchema;
115
+ onSchemaChange(rest);
116
+ }
117
+ } else {
118
+ let parsedValue = value;
119
+ if ("number" === type || "integer" === type) {
120
+ parsedValue = Number(value);
121
+ if (Number.isNaN(parsedValue)) parsedValue = value;
122
+ }
123
+ if (JSON.stringify(currentDefault) !== JSON.stringify(parsedValue)) onSchemaChange({
124
+ ...objSchema,
125
+ default: parsedValue
126
+ });
127
+ }
128
+ };
81
129
  return /*#__PURE__*/ jsxs("div", {
82
130
  className: cn("mb-2 animate-in rounded-lg border transition-all duration-200", depth > 0 && "ml-0 sm:ml-4 border-l border-l-border/40"),
83
131
  children: [
@@ -221,13 +269,40 @@ const SchemaPropertyEditor = ({ name, schema, required, readOnly = false, parent
221
269
  className: "text-xs font-medium text-muted-foreground",
222
270
  children: t.propertyDefaultLabel
223
271
  }),
224
- /*#__PURE__*/ jsx(Input, {
272
+ defaultValueOptions && defaultValueOptions.length > 0 ? /*#__PURE__*/ jsxs(Select, {
273
+ value: "" !== defaultValue && defaultValueOptions.includes(defaultValue) ? defaultValue : "__none__",
274
+ onValueChange: handleDefaultSelectChange,
275
+ children: [
276
+ /*#__PURE__*/ jsx(SelectTrigger, {
277
+ className: "h-8 text-sm",
278
+ children: /*#__PURE__*/ jsx(SelectValue, {
279
+ placeholder: t.propertyDefaultPlaceholder
280
+ })
281
+ }),
282
+ /*#__PURE__*/ jsxs(SelectContent, {
283
+ children: [
284
+ /*#__PURE__*/ jsx(SelectItem, {
285
+ value: "__none__",
286
+ children: t.propertyDefaultNone
287
+ }),
288
+ defaultValueOptions.map((option)=>/*#__PURE__*/ jsx(SelectItem, {
289
+ value: option,
290
+ children: option
291
+ }, option))
292
+ ]
293
+ })
294
+ ]
295
+ }) : /*#__PURE__*/ jsx(Input, {
225
296
  value: tempDefault,
226
297
  onChange: (e)=>setTempDefault(e.target.value),
227
298
  onBlur: handleDefaultSubmit,
228
299
  onKeyDown: (e)=>"Enter" === e.key && handleDefaultSubmit(),
229
300
  placeholder: t.propertyDefaultPlaceholder,
230
301
  className: "h-8 text-sm"
302
+ }),
303
+ defaultError && /*#__PURE__*/ jsx("div", {
304
+ className: "text-xs text-destructive italic",
305
+ children: defaultError
231
306
  })
232
307
  ]
233
308
  }),
@@ -41,6 +41,9 @@ const de = {
41
41
  propertyMoveDown: "Feld nach unten verschieben",
42
42
  propertyDefaultLabel: "Standardwert",
43
43
  propertyDefaultPlaceholder: "Standardwert eingeben...",
44
+ propertyDefaultMustBeInEnum: "Der Standardwert muss einer der erlaubten Werte sein.",
45
+ propertyDefaultRequiresDependentDefault: "Ein Standardwert kann nur gesetzt werden, wenn die abhängige Eigenschaft einen Standardwert hat.",
46
+ propertyDefaultNone: "Kein Standardwert",
44
47
  schemaEditorTitle: "JSON-Schema-Editor",
45
48
  schemaEditorToggleFullscreen: "Vollbild umschalten",
46
49
  schemaEditorEditModeVisual: "Visuell",
@@ -41,6 +41,9 @@ const en = {
41
41
  propertyMoveDown: "Move field down",
42
42
  propertyDefaultLabel: "Default Value",
43
43
  propertyDefaultPlaceholder: "Enter default value...",
44
+ propertyDefaultMustBeInEnum: "Default value must be one of the allowed values.",
45
+ propertyDefaultRequiresDependentDefault: "Default can only be set when the dependent property has a default.",
46
+ propertyDefaultNone: "No default",
44
47
  schemaEditorTitle: "JSON Schema Editor",
45
48
  schemaEditorToggleFullscreen: "Toggle fullscreen",
46
49
  schemaEditorEditModeVisual: "Visual",
@@ -41,6 +41,9 @@ const es = {
41
41
  propertyMoveDown: "Mover campo hacia abajo",
42
42
  propertyDefaultLabel: "Valor por defecto",
43
43
  propertyDefaultPlaceholder: "Ingresar valor por defecto...",
44
+ propertyDefaultMustBeInEnum: "El valor por defecto debe ser uno de los valores permitidos.",
45
+ propertyDefaultRequiresDependentDefault: "El valor por defecto solo puede establecerse cuando la propiedad dependiente tiene un valor por defecto.",
46
+ propertyDefaultNone: "Sin valor por defecto",
44
47
  schemaEditorTitle: "Editor de JSON Schema",
45
48
  schemaEditorToggleFullscreen: "Cambiar a pantalla completa",
46
49
  schemaEditorEditModeVisual: "Visual",
@@ -41,6 +41,9 @@ const fr = {
41
41
  propertyMoveDown: "Déplacer le champ vers le bas",
42
42
  propertyDefaultLabel: "Valeur par défaut",
43
43
  propertyDefaultPlaceholder: "Entrer la valeur par défaut...",
44
+ propertyDefaultMustBeInEnum: "La valeur par défaut doit être l'une des valeurs autorisées.",
45
+ propertyDefaultRequiresDependentDefault: "La valeur par défaut ne peut être définie que lorsque la propriété dépendante a une valeur par défaut.",
46
+ propertyDefaultNone: "Aucune valeur par défaut",
44
47
  schemaEditorTitle: "Éditeur de schéma JSON",
45
48
  schemaEditorToggleFullscreen: "Basculer en plein écran",
46
49
  schemaEditorEditModeVisual: "Visuel",
@@ -41,6 +41,9 @@ const ru = {
41
41
  propertyMoveDown: "Переместить поле вниз",
42
42
  propertyDefaultLabel: "Значение по умолчанию",
43
43
  propertyDefaultPlaceholder: "Введите значение по умолчанию...",
44
+ propertyDefaultMustBeInEnum: "Значение по умолчанию должно быть одним из разрешенных значений.",
45
+ propertyDefaultRequiresDependentDefault: "Значение по умолчанию может быть установлено только тогда, когда зависимое свойство имеет значение по умолчанию.",
46
+ propertyDefaultNone: "Нет значения по умолчанию",
44
47
  schemaEditorTitle: "Редактор JSON схем",
45
48
  schemaEditorToggleFullscreen: "Переключить полноэкранный режим",
46
49
  schemaEditorEditModeVisual: "Визуальный",
@@ -41,6 +41,9 @@ const uk = {
41
41
  propertyMoveDown: "Перемістити поле вниз",
42
42
  propertyDefaultLabel: "Значення за замовчуванням",
43
43
  propertyDefaultPlaceholder: "Введіть значення за замовчуванням...",
44
+ propertyDefaultMustBeInEnum: "Значення за замовчуванням повинно бути одним з дозволених значень.",
45
+ propertyDefaultRequiresDependentDefault: "Значення за замовчуванням може бути встановлено лише тоді, коли залежна властивість має значення за замовчуванням.",
46
+ propertyDefaultNone: "Немає значення за замовчуванням",
44
47
  schemaEditorTitle: "Редактор JSON-схем",
45
48
  schemaEditorToggleFullscreen: "Перемкнути повноекранний режим",
46
49
  schemaEditorEditModeVisual: "Візуальний",
@@ -41,6 +41,9 @@ const zh = {
41
41
  propertyMoveDown: "向下移动字段",
42
42
  propertyDefaultLabel: "默认值",
43
43
  propertyDefaultPlaceholder: "输入默认值...",
44
+ propertyDefaultMustBeInEnum: "默认值必须是允许的值之一。",
45
+ propertyDefaultRequiresDependentDefault: "只有在依赖属性具有默认值时才能设置默认值。",
46
+ propertyDefaultNone: "无默认值",
44
47
  schemaEditorTitle: "JSON Schema 编辑器",
45
48
  schemaEditorToggleFullscreen: "切换全屏",
46
49
  schemaEditorEditModeVisual: "可视化",
package/dist/index.d.ts CHANGED
@@ -400,6 +400,24 @@ export declare interface Translation {
400
400
  * > Enter default value...
401
401
  */
402
402
  readonly propertyDefaultPlaceholder: string;
403
+ /**
404
+ * The translation for the key `propertyDefaultMustBeInEnum`. English default is:
405
+ *
406
+ * > Default value must be one of the allowed values.
407
+ */
408
+ readonly propertyDefaultMustBeInEnum: string;
409
+ /**
410
+ * The translation for the key `propertyDefaultRequiresDependentDefault`. English default is:
411
+ *
412
+ * > Default can only be set when the dependent property has a default.
413
+ */
414
+ readonly propertyDefaultRequiresDependentDefault: string;
415
+ /**
416
+ * The translation for the key `propertyDefaultNone`. English default is:
417
+ *
418
+ * > No default
419
+ */
420
+ readonly propertyDefaultNone: string;
403
421
  /**
404
422
  * The translation for the key `arrayNoConstraint`. English default is:
405
423
  *
@@ -138,7 +138,75 @@ function validateSchemaByType(schema, type, t) {
138
138
  errors: result.error.issues
139
139
  };
140
140
  }
141
- function buildValidationTree(schema, t) {
141
+ function validateDefaultInEnum(schema, type, t, parentSchema) {
142
+ if ("string" !== type && "number" !== type && "integer" !== type) return null;
143
+ const defaultValue = schema.default;
144
+ if (void 0 === defaultValue) return null;
145
+ const enumValues = schema.enum;
146
+ if (Array.isArray(enumValues) && enumValues.length > 0) {
147
+ const isInEnum = enumValues.some((val)=>{
148
+ if ("number" === type || "integer" === type) return val === defaultValue || "number" == typeof val && "number" == typeof defaultValue && val === defaultValue;
149
+ return String(val) === String(defaultValue);
150
+ });
151
+ if (!isInEnum) return {
152
+ code: "custom",
153
+ message: t.propertyDefaultMustBeInEnum,
154
+ path: [
155
+ "default"
156
+ ]
157
+ };
158
+ }
159
+ const dependentEnum = schema.$dependentEnum;
160
+ if (dependentEnum) {
161
+ const controllingPropertyName = dependentEnum.property;
162
+ const dependentValuesMap = dependentEnum.values;
163
+ if (!parentSchema) return {
164
+ code: "custom",
165
+ message: t.propertyDefaultRequiresDependentDefault,
166
+ path: [
167
+ "default"
168
+ ]
169
+ };
170
+ const controllingPropertySchema = parentSchema.properties?.[controllingPropertyName];
171
+ if (!controllingPropertySchema || "object" != typeof controllingPropertySchema) return {
172
+ code: "custom",
173
+ message: t.propertyDefaultRequiresDependentDefault,
174
+ path: [
175
+ "default"
176
+ ]
177
+ };
178
+ const controllingDefault = controllingPropertySchema.default;
179
+ if (void 0 === controllingDefault) return {
180
+ code: "custom",
181
+ message: t.propertyDefaultRequiresDependentDefault,
182
+ path: [
183
+ "default"
184
+ ]
185
+ };
186
+ const ctrlDefaultStr = String(controllingDefault);
187
+ const allowedValues = dependentValuesMap[ctrlDefaultStr];
188
+ if (!Array.isArray(allowedValues) || 0 === allowedValues.length) return {
189
+ code: "custom",
190
+ message: t.propertyDefaultRequiresDependentDefault,
191
+ path: [
192
+ "default"
193
+ ]
194
+ };
195
+ const isInAllowedValues = allowedValues.some((val)=>{
196
+ if ("number" === type || "integer" === type) return val === defaultValue || "number" == typeof val && "number" == typeof defaultValue && val === defaultValue;
197
+ return String(val) === String(defaultValue);
198
+ });
199
+ if (!isInAllowedValues) return {
200
+ code: "custom",
201
+ message: t.propertyDefaultMustBeInEnum,
202
+ path: [
203
+ "default"
204
+ ]
205
+ };
206
+ }
207
+ return null;
208
+ }
209
+ function buildValidationTree(schema, t, parentSchema) {
142
210
  const deriveType = (sch)=>{
143
211
  if (!sch || "object" != typeof sch) return;
144
212
  const declared = sch.type;
@@ -169,20 +237,33 @@ function buildValidationTree(schema, t) {
169
237
  const sch = schema;
170
238
  const currentType = deriveType(sch);
171
239
  const validation = validateSchemaByType(schema, currentType, t);
240
+ if ("string" === currentType || "number" === currentType || "integer" === currentType) {
241
+ const defaultError = validateDefaultInEnum(schema, currentType, t, parentSchema);
242
+ if (defaultError) if (validation.success) {
243
+ validation.success = false;
244
+ validation.errors = [
245
+ defaultError
246
+ ];
247
+ } else validation.errors = [
248
+ ...validation.errors || [],
249
+ defaultError
250
+ ];
251
+ }
172
252
  const children = {};
173
253
  if ("object" === currentType) {
254
+ const currentObjectSchema = schema;
174
255
  const properties = sch.properties;
175
- if (properties && "object" == typeof properties) for (const [propName, propSchema] of Object.entries(properties))children[propName] = buildValidationTree(propSchema, t);
176
- if (sch.patternProperties && "object" == typeof sch.patternProperties) for (const [patternName, patternSchema] of Object.entries(sch.patternProperties))children[`pattern:${patternName}`] = buildValidationTree(patternSchema, t);
256
+ if (properties && "object" == typeof properties) for (const [propName, propSchema] of Object.entries(properties))children[propName] = buildValidationTree(propSchema, t, currentObjectSchema);
257
+ if (sch.patternProperties && "object" == typeof sch.patternProperties) for (const [patternName, patternSchema] of Object.entries(sch.patternProperties))children[`pattern:${patternName}`] = buildValidationTree(patternSchema, t, currentObjectSchema);
177
258
  }
178
259
  if ("array" === currentType) {
179
260
  const items = sch.items;
180
261
  if (Array.isArray(items)) items.forEach((it, idx)=>{
181
- children[`items[${idx}]`] = buildValidationTree(it, t);
262
+ children[`items[${idx}]`] = buildValidationTree(it, t, parentSchema);
182
263
  });
183
- else if (items) children.items = buildValidationTree(items, t);
264
+ else if (items) children.items = buildValidationTree(items, t, parentSchema);
184
265
  if (Array.isArray(sch.prefixItems)) sch.prefixItems.forEach((it, idx)=>{
185
- children[`prefixItems[${idx}]`] = buildValidationTree(it, t);
266
+ children[`prefixItems[${idx}]`] = buildValidationTree(it, t, parentSchema);
186
267
  });
187
268
  }
188
269
  const combinators = [
@@ -196,13 +277,13 @@ function buildValidationTree(schema, t) {
196
277
  children[[
197
278
  comb,
198
279
  idx
199
- ].join(":")] = buildValidationTree(subSchema, t);
280
+ ].join(":")] = buildValidationTree(subSchema, t, parentSchema);
200
281
  });
201
282
  }
202
- if (sch.not) children.not = buildValidationTree(sch.not, t);
203
- if (sch.$defs && "object" == typeof sch.$defs) for (const [defName, defSchema] of Object.entries(sch.$defs))children[`$defs:${defName}`] = buildValidationTree(defSchema, t);
283
+ if (sch.not) children.not = buildValidationTree(sch.not, t, parentSchema);
284
+ if (sch.$defs && "object" == typeof sch.$defs) for (const [defName, defSchema] of Object.entries(sch.$defs))children[`$defs:${defName}`] = buildValidationTree(defSchema, t, parentSchema);
204
285
  const definitions = sch.definitions;
205
- if (definitions && "object" == typeof definitions) for (const [defName, defSchema] of Object.entries(definitions))children[`definitions:${defName}`] = buildValidationTree(defSchema, t);
286
+ if (definitions && "object" == typeof definitions) for (const [defName, defSchema] of Object.entries(definitions))children[`definitions:${defName}`] = buildValidationTree(defSchema, t, parentSchema);
206
287
  const ownErrors = validation.success ? 0 : validation.errors?.length ?? 0;
207
288
  const childrenErrors = Object.values(children).reduce((sum, child)=>sum + child.cumulativeChildrenErrors, 0);
208
289
  return {
package/package.json CHANGED
@@ -14,7 +14,7 @@
14
14
  "react"
15
15
  ],
16
16
  "private": false,
17
- "version": "0.4.0",
17
+ "version": "0.5.0",
18
18
  "type": "module",
19
19
  "sideEffects": false,
20
20
  "repository": {