@petrarca/sonnet-schema-designer 0.5.0 → 0.7.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.
- package/README.md +97 -0
- package/dist/index.d.ts +346 -36
- package/dist/index.js +3056 -608
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,42 +1,258 @@
|
|
|
1
1
|
// src/model/paths.ts
|
|
2
|
+
import {
|
|
3
|
+
isOneOf,
|
|
4
|
+
getDiscriminator,
|
|
5
|
+
getBranchLabel
|
|
6
|
+
} from "@petrarca/sonnet-core/schema";
|
|
7
|
+
function isReservedKey(key) {
|
|
8
|
+
return /^#(branch|condition|def|slot):/.test(key);
|
|
9
|
+
}
|
|
10
|
+
function prop(key) {
|
|
11
|
+
return { kind: "prop", key };
|
|
12
|
+
}
|
|
13
|
+
function branch(index) {
|
|
14
|
+
return { kind: "branch", index };
|
|
15
|
+
}
|
|
16
|
+
function condition(index, combinator = "allOf") {
|
|
17
|
+
return { kind: "condition", combinator, index };
|
|
18
|
+
}
|
|
19
|
+
function slot(name) {
|
|
20
|
+
return { kind: "slot", slot: name };
|
|
21
|
+
}
|
|
22
|
+
function def(name) {
|
|
23
|
+
return { kind: "def", name };
|
|
24
|
+
}
|
|
25
|
+
var SEP = "/";
|
|
26
|
+
function escapeToken(raw) {
|
|
27
|
+
return raw.replace(/\\/g, "\\\\").replace(/\//g, "\\/");
|
|
28
|
+
}
|
|
29
|
+
function unescapeToken(raw) {
|
|
30
|
+
return raw.replace(/\\\//g, "/").replace(/\\\\/g, "\\");
|
|
31
|
+
}
|
|
32
|
+
function encodeSegment(seg) {
|
|
33
|
+
switch (seg.kind) {
|
|
34
|
+
case "prop":
|
|
35
|
+
return escapeToken(seg.key);
|
|
36
|
+
case "branch":
|
|
37
|
+
return `#branch:${seg.index}`;
|
|
38
|
+
case "condition":
|
|
39
|
+
return `#condition:${seg.combinator}:${seg.index}`;
|
|
40
|
+
case "slot":
|
|
41
|
+
return `#slot:${seg.slot}`;
|
|
42
|
+
case "def":
|
|
43
|
+
return `#def:${escapeToken(seg.name)}`;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
var VALID_SLOTS = ["then", "else", "items"];
|
|
47
|
+
function safeIndex(raw) {
|
|
48
|
+
const n = Number(raw);
|
|
49
|
+
return Number.isInteger(n) && n >= 0 ? n : 0;
|
|
50
|
+
}
|
|
51
|
+
function decodeCondition(rest) {
|
|
52
|
+
const sep = rest.indexOf(":");
|
|
53
|
+
const combinatorRaw = sep >= 0 ? rest.slice(0, sep) : "allOf";
|
|
54
|
+
const combinator = combinatorRaw === "anyOf" ? "anyOf" : "allOf";
|
|
55
|
+
const index = safeIndex(sep >= 0 ? rest.slice(sep + 1) : rest);
|
|
56
|
+
return { kind: "condition", combinator, index };
|
|
57
|
+
}
|
|
58
|
+
function decodeSegment(token) {
|
|
59
|
+
if (token.startsWith("#branch:")) {
|
|
60
|
+
return { kind: "branch", index: safeIndex(token.slice("#branch:".length)) };
|
|
61
|
+
}
|
|
62
|
+
if (token.startsWith("#condition:")) {
|
|
63
|
+
return decodeCondition(token.slice("#condition:".length));
|
|
64
|
+
}
|
|
65
|
+
if (token.startsWith("#slot:")) {
|
|
66
|
+
const s = token.slice("#slot:".length);
|
|
67
|
+
return { kind: "slot", slot: VALID_SLOTS.includes(s) ? s : "items" };
|
|
68
|
+
}
|
|
69
|
+
if (token.startsWith("#def:")) {
|
|
70
|
+
return { kind: "def", name: unescapeToken(token.slice("#def:".length)) };
|
|
71
|
+
}
|
|
72
|
+
return { kind: "prop", key: unescapeToken(token) };
|
|
73
|
+
}
|
|
74
|
+
function splitTokens(key) {
|
|
75
|
+
const tokens = [];
|
|
76
|
+
let current = "";
|
|
77
|
+
for (let i = 0; i < key.length; i++) {
|
|
78
|
+
const ch = key[i];
|
|
79
|
+
if (ch === "\\" && i + 1 < key.length) {
|
|
80
|
+
current += ch + key[i + 1];
|
|
81
|
+
i++;
|
|
82
|
+
} else if (ch === SEP) {
|
|
83
|
+
tokens.push(current);
|
|
84
|
+
current = "";
|
|
85
|
+
} else {
|
|
86
|
+
current += ch;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
tokens.push(current);
|
|
90
|
+
return tokens;
|
|
91
|
+
}
|
|
2
92
|
function pathKey(path) {
|
|
3
|
-
return path.join(
|
|
93
|
+
return path.map(encodeSegment).join(SEP);
|
|
94
|
+
}
|
|
95
|
+
function parsePathKey(key) {
|
|
96
|
+
if (key === "") return [];
|
|
97
|
+
return splitTokens(key).map(decodeSegment);
|
|
98
|
+
}
|
|
99
|
+
function segmentIdentity(seg) {
|
|
100
|
+
switch (seg.kind) {
|
|
101
|
+
case "prop":
|
|
102
|
+
return `prop:${seg.key}`;
|
|
103
|
+
case "branch":
|
|
104
|
+
return `branch:${seg.index}`;
|
|
105
|
+
case "condition":
|
|
106
|
+
return `condition:${seg.combinator}:${seg.index}`;
|
|
107
|
+
case "slot":
|
|
108
|
+
return `slot:${seg.slot}`;
|
|
109
|
+
case "def":
|
|
110
|
+
return `def:${seg.name}`;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function sameSegment(a, b) {
|
|
114
|
+
return segmentIdentity(a) === segmentIdentity(b);
|
|
4
115
|
}
|
|
5
116
|
function samePath(a, b) {
|
|
6
117
|
if (a === null || b === null) return a === b;
|
|
7
|
-
return a.length === b.length && a.every((seg, i) => seg
|
|
118
|
+
return a.length === b.length && a.every((seg, i) => sameSegment(seg, b[i]));
|
|
8
119
|
}
|
|
9
|
-
function childContainer(
|
|
10
|
-
|
|
11
|
-
if (
|
|
12
|
-
return
|
|
120
|
+
function childContainer(prop2) {
|
|
121
|
+
const typeIsObjectLike = prop2.type === "object" || prop2.type === void 0;
|
|
122
|
+
if (typeIsObjectLike && prop2.properties && !prop2.oneOf)
|
|
123
|
+
return prop2.properties;
|
|
124
|
+
if (prop2.type === "array" && prop2.items?.type === "object") {
|
|
125
|
+
return prop2.items.properties;
|
|
13
126
|
}
|
|
14
127
|
return void 0;
|
|
15
128
|
}
|
|
129
|
+
function isConditionalMember(member) {
|
|
130
|
+
return !!member.if;
|
|
131
|
+
}
|
|
132
|
+
function membersOfCombinator(property, combinator) {
|
|
133
|
+
const arr = property[combinator];
|
|
134
|
+
if (!Array.isArray(arr)) return [];
|
|
135
|
+
return arr.map((member, index) => ({ member, combinator, index })).filter(({ member }) => isConditionalMember(member));
|
|
136
|
+
}
|
|
137
|
+
function conditionalMembers(property) {
|
|
138
|
+
return [
|
|
139
|
+
...membersOfCombinator(property, "allOf"),
|
|
140
|
+
...membersOfCombinator(property, "anyOf")
|
|
141
|
+
];
|
|
142
|
+
}
|
|
143
|
+
function conditionLabel(member) {
|
|
144
|
+
const field = Object.keys(member.if?.properties ?? {})[0] ?? member.if?.required?.[0];
|
|
145
|
+
return field ? `if ${field}` : "if ...";
|
|
146
|
+
}
|
|
147
|
+
function conditionalSlots(member) {
|
|
148
|
+
const slots = [];
|
|
149
|
+
const add = (name, sub) => {
|
|
150
|
+
if (sub) {
|
|
151
|
+
slots.push({
|
|
152
|
+
segment: slot(name),
|
|
153
|
+
label: name,
|
|
154
|
+
property: sub,
|
|
155
|
+
kind: "conditional",
|
|
156
|
+
removable: false,
|
|
157
|
+
reorderable: false
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
add("then", member.then);
|
|
162
|
+
add("else", member.else);
|
|
163
|
+
return slots;
|
|
164
|
+
}
|
|
165
|
+
function definitionsOf(property) {
|
|
166
|
+
return property.$defs ?? property.definitions;
|
|
167
|
+
}
|
|
168
|
+
function definitionSlots(property) {
|
|
169
|
+
const defs = definitionsOf(property);
|
|
170
|
+
if (!defs) return [];
|
|
171
|
+
return Object.keys(defs).map((name) => ({
|
|
172
|
+
segment: def(name),
|
|
173
|
+
label: name,
|
|
174
|
+
property: defs[name],
|
|
175
|
+
kind: "definition",
|
|
176
|
+
removable: true,
|
|
177
|
+
reorderable: false
|
|
178
|
+
}));
|
|
179
|
+
}
|
|
180
|
+
function childSlots(property) {
|
|
181
|
+
if (isConditionalMember(property)) return conditionalSlots(property);
|
|
182
|
+
if (isOneOf(property)) {
|
|
183
|
+
const discriminator = getDiscriminator(property);
|
|
184
|
+
return (property.oneOf ?? []).map((b, index) => ({
|
|
185
|
+
segment: branch(index),
|
|
186
|
+
label: getBranchLabel(b, discriminator, index),
|
|
187
|
+
property: b,
|
|
188
|
+
kind: "branch",
|
|
189
|
+
removable: true,
|
|
190
|
+
reorderable: true
|
|
191
|
+
}));
|
|
192
|
+
}
|
|
193
|
+
const container = childContainer(property);
|
|
194
|
+
const propertySlots = container ? Object.keys(container).map((key) => ({
|
|
195
|
+
segment: prop(key),
|
|
196
|
+
label: key,
|
|
197
|
+
property: container[key],
|
|
198
|
+
kind: "property",
|
|
199
|
+
removable: true,
|
|
200
|
+
reorderable: true
|
|
201
|
+
})) : [];
|
|
202
|
+
const conditionSlots = conditionalMembers(property).map(
|
|
203
|
+
({ member, combinator, index }) => ({
|
|
204
|
+
segment: condition(index, combinator),
|
|
205
|
+
label: conditionLabel(member),
|
|
206
|
+
property: member,
|
|
207
|
+
kind: "conditional",
|
|
208
|
+
removable: true,
|
|
209
|
+
reorderable: false
|
|
210
|
+
})
|
|
211
|
+
);
|
|
212
|
+
return [...propertySlots, ...conditionSlots, ...definitionSlots(property)];
|
|
213
|
+
}
|
|
214
|
+
function stepInto(parent, seg) {
|
|
215
|
+
if (seg.kind === "prop") return childContainer(parent)?.[seg.key];
|
|
216
|
+
if (seg.kind === "branch") return parent.oneOf?.[seg.index];
|
|
217
|
+
if (seg.kind === "condition") return parent[seg.combinator]?.[seg.index];
|
|
218
|
+
if (seg.kind === "def") return definitionsOf(parent)?.[seg.name];
|
|
219
|
+
if (seg.slot === "items") return parent.items;
|
|
220
|
+
return parent[seg.slot];
|
|
221
|
+
}
|
|
222
|
+
function schemaAsRootProp(schema) {
|
|
223
|
+
return {
|
|
224
|
+
type: "object",
|
|
225
|
+
properties: schema.properties,
|
|
226
|
+
required: schema.required,
|
|
227
|
+
"x-ui-order": schema["x-ui-order"],
|
|
228
|
+
allOf: schema.allOf,
|
|
229
|
+
anyOf: schema.anyOf,
|
|
230
|
+
$defs: schema.$defs,
|
|
231
|
+
definitions: schema.definitions
|
|
232
|
+
};
|
|
233
|
+
}
|
|
16
234
|
function getProperty(schema, path) {
|
|
17
235
|
if (path.length === 0) return void 0;
|
|
18
|
-
|
|
19
|
-
let
|
|
236
|
+
const root = schemaAsRootProp(schema);
|
|
237
|
+
let current = root;
|
|
20
238
|
for (const seg of path) {
|
|
21
|
-
if (!
|
|
22
|
-
|
|
23
|
-
if (!prop) return void 0;
|
|
24
|
-
container = childContainer(prop);
|
|
239
|
+
if (!current) return void 0;
|
|
240
|
+
current = stepInto(current, seg);
|
|
25
241
|
}
|
|
26
|
-
return
|
|
27
|
-
}
|
|
28
|
-
function isContainer(prop) {
|
|
29
|
-
return childContainer(prop) !== void 0;
|
|
242
|
+
return current;
|
|
30
243
|
}
|
|
31
|
-
function
|
|
32
|
-
return childContainer(
|
|
244
|
+
function isContainer(prop2) {
|
|
245
|
+
return childSlots(prop2).length > 0 || childContainer(prop2) !== void 0;
|
|
33
246
|
}
|
|
34
247
|
|
|
35
248
|
// src/model/view.ts
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (
|
|
39
|
-
if (
|
|
249
|
+
import { isOneOf as isOneOf2 } from "@petrarca/sonnet-core/schema";
|
|
250
|
+
function propertyKind(prop2) {
|
|
251
|
+
if (prop2.if) return "conditional";
|
|
252
|
+
if (isOneOf2(prop2)) return "oneOf";
|
|
253
|
+
if (prop2.type === "object" && prop2.properties) return "object";
|
|
254
|
+
if (prop2.type === "array") return "array";
|
|
255
|
+
if (Array.isArray(prop2.enum) && prop2.enum.length > 0) return "enum";
|
|
40
256
|
return "scalar";
|
|
41
257
|
}
|
|
42
258
|
function arrayCardinality(min, max) {
|
|
@@ -44,12 +260,14 @@ function arrayCardinality(min, max) {
|
|
|
44
260
|
if (min != null && min === max) return `${min}`;
|
|
45
261
|
return `${min ?? ""}..${max ?? ""}`;
|
|
46
262
|
}
|
|
47
|
-
function typeLabelOf(
|
|
48
|
-
if (
|
|
49
|
-
|
|
50
|
-
|
|
263
|
+
function typeLabelOf(prop2) {
|
|
264
|
+
if (prop2.if) return "if/then/else";
|
|
265
|
+
if (isOneOf2(prop2)) return "oneOf";
|
|
266
|
+
if (prop2.type === "array") {
|
|
267
|
+
const itemType = prop2.items?.type ?? "any";
|
|
268
|
+
return `${itemType}[${arrayCardinality(prop2.minItems, prop2.maxItems)}]`;
|
|
51
269
|
}
|
|
52
|
-
return
|
|
270
|
+
return prop2.type ?? "any";
|
|
53
271
|
}
|
|
54
272
|
function orderedKeys(properties, order) {
|
|
55
273
|
const keys = Object.keys(properties);
|
|
@@ -58,53 +276,165 @@ function orderedKeys(properties, order) {
|
|
|
58
276
|
const rest = keys.filter((k) => !inOrder.includes(k));
|
|
59
277
|
return [...inOrder, ...rest];
|
|
60
278
|
}
|
|
61
|
-
function
|
|
62
|
-
|
|
63
|
-
|
|
279
|
+
function requiredOf(prop2) {
|
|
280
|
+
if (prop2.type === "object") return prop2.required;
|
|
281
|
+
if (prop2.type === "array") return prop2.items?.required;
|
|
282
|
+
return void 0;
|
|
283
|
+
}
|
|
284
|
+
function orderOf(prop2) {
|
|
285
|
+
if (prop2.type === "array") return prop2.items?.["x-ui-order"];
|
|
286
|
+
return prop2["x-ui-order"];
|
|
287
|
+
}
|
|
288
|
+
function nodeKind(slot2, prop2) {
|
|
289
|
+
if (slot2.kind === "branch") return "object";
|
|
290
|
+
if (slot2.segment.kind === "def") return "definition";
|
|
291
|
+
if (slot2.segment.kind === "slot") return "object";
|
|
292
|
+
return propertyKind(prop2);
|
|
293
|
+
}
|
|
294
|
+
function requiredForChildren(prop2, isSlot) {
|
|
295
|
+
return isSlot ? prop2.required : requiredOf(prop2);
|
|
296
|
+
}
|
|
297
|
+
function resolveRefTarget(prop2, resolve) {
|
|
298
|
+
if (!prop2.$ref || !resolve) return void 0;
|
|
299
|
+
const target = resolve(prop2.$ref);
|
|
300
|
+
if (!target) return void 0;
|
|
301
|
+
const { $ref: _drop, ...overrides } = prop2;
|
|
302
|
+
return { ...target, ...overrides };
|
|
303
|
+
}
|
|
304
|
+
function refShapeOf(slot2, prop2, resolve) {
|
|
305
|
+
const refTarget = resolveRefTarget(prop2, resolve);
|
|
306
|
+
if (refTarget) {
|
|
307
|
+
return {
|
|
308
|
+
kind: "reference",
|
|
309
|
+
typeLabel: refLabel(prop2.$ref),
|
|
310
|
+
reference: prop2.$ref,
|
|
311
|
+
container: refTarget,
|
|
312
|
+
childrenFromRef: true
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
if (prop2.type === "array" && prop2.items?.$ref) {
|
|
316
|
+
const itemTarget = resolveRefTarget(prop2.items, resolve);
|
|
317
|
+
if (itemTarget) {
|
|
318
|
+
return {
|
|
319
|
+
kind: "array",
|
|
320
|
+
typeLabel: arrayItemLabel(prop2.items.$ref, prop2),
|
|
321
|
+
reference: prop2.items.$ref,
|
|
322
|
+
container: itemTarget,
|
|
323
|
+
childrenFromRef: true
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return {
|
|
328
|
+
kind: nodeKind(slot2, prop2),
|
|
329
|
+
typeLabel: typeLabelOf(prop2),
|
|
330
|
+
container: prop2,
|
|
331
|
+
childrenFromRef: false
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
function buildNode(slot2, parentPath, required, resolve, inRef) {
|
|
335
|
+
const path = [...parentPath, slot2.segment];
|
|
336
|
+
const prop2 = slot2.property;
|
|
337
|
+
const widget = typeof prop2["x-ui-widget"] === "string" ? prop2["x-ui-widget"] : void 0;
|
|
338
|
+
const ref = refShapeOf(slot2, prop2, resolve);
|
|
64
339
|
const node = {
|
|
65
340
|
path,
|
|
66
|
-
key,
|
|
67
|
-
property:
|
|
68
|
-
kind:
|
|
69
|
-
|
|
70
|
-
|
|
341
|
+
key: slot2.label,
|
|
342
|
+
property: prop2,
|
|
343
|
+
kind: ref.kind,
|
|
344
|
+
slotKind: slot2.kind,
|
|
345
|
+
required,
|
|
346
|
+
typeLabel: ref.typeLabel,
|
|
71
347
|
widget,
|
|
348
|
+
reference: ref.reference,
|
|
349
|
+
readOnly: inRef || void 0,
|
|
72
350
|
children: []
|
|
73
351
|
};
|
|
74
|
-
if (isContainer(
|
|
75
|
-
const
|
|
76
|
-
const
|
|
77
|
-
const
|
|
78
|
-
node.children =
|
|
79
|
-
(
|
|
352
|
+
if (isContainer(ref.container)) {
|
|
353
|
+
const isSlot = slot2.segment.kind === "slot";
|
|
354
|
+
const req = requiredForChildren(ref.container, isSlot);
|
|
355
|
+
const childrenInRef = inRef || ref.childrenFromRef;
|
|
356
|
+
node.children = orderedSlots(ref.container).map(
|
|
357
|
+
(child) => buildNode(
|
|
358
|
+
child,
|
|
359
|
+
path,
|
|
360
|
+
child.kind === "property" ? !!req?.includes(child.label) : false,
|
|
361
|
+
resolve,
|
|
362
|
+
childrenInRef
|
|
363
|
+
)
|
|
80
364
|
);
|
|
81
365
|
}
|
|
82
366
|
return node;
|
|
83
367
|
}
|
|
84
|
-
function
|
|
85
|
-
|
|
86
|
-
|
|
368
|
+
function refName(ref) {
|
|
369
|
+
const local = ref.match(/^#\/(?:\$defs|definitions)\/(.+)$/);
|
|
370
|
+
return local ? local[1] : ref.split("/").pop() || ref;
|
|
371
|
+
}
|
|
372
|
+
function refLabel(ref) {
|
|
373
|
+
return `\u2192 ${refName(ref)}`;
|
|
374
|
+
}
|
|
375
|
+
function arrayItemLabel(ref, prop2) {
|
|
376
|
+
return `${refName(ref)}[${arrayCardinality(prop2.minItems, prop2.maxItems)}]`;
|
|
377
|
+
}
|
|
378
|
+
function orderedSlots(prop2) {
|
|
379
|
+
const slots = childSlots(prop2);
|
|
380
|
+
const propertySlots = slots.filter((s) => s.kind === "property");
|
|
381
|
+
const otherSlots = slots.filter((s) => s.kind !== "property");
|
|
382
|
+
const order = orderOf(prop2);
|
|
383
|
+
if (propertySlots.length === 0 || !order || order.length === 0) return slots;
|
|
384
|
+
const byLabel = new Map(propertySlots.map((s) => [s.label, s]));
|
|
385
|
+
const inOrder = order.filter((k) => byLabel.has(k)).map((k) => byLabel.get(k));
|
|
386
|
+
const rest = propertySlots.filter((s) => !order.includes(s.label));
|
|
387
|
+
return [...inOrder, ...rest, ...otherSlots];
|
|
388
|
+
}
|
|
389
|
+
function buildPropertyTree(schema, resolve) {
|
|
390
|
+
const effective = withInDocumentFallback(schema, resolve);
|
|
391
|
+
const rootProp = schemaAsRootProp(schema);
|
|
392
|
+
return orderedSlots(rootProp).map(
|
|
393
|
+
(slot2) => buildNode(
|
|
394
|
+
slot2,
|
|
395
|
+
[],
|
|
396
|
+
!!schema.required?.includes(slot2.label),
|
|
397
|
+
effective,
|
|
398
|
+
false
|
|
399
|
+
)
|
|
87
400
|
);
|
|
88
401
|
}
|
|
402
|
+
function withInDocumentFallback(schema, resolve) {
|
|
403
|
+
const defs = schema.$defs ?? schema.definitions;
|
|
404
|
+
if (!defs) return resolve;
|
|
405
|
+
const local = (ref) => {
|
|
406
|
+
const m = ref.match(/^#\/(?:\$defs|definitions)\/(.+)$/);
|
|
407
|
+
return m ? defs[m[1]] : void 0;
|
|
408
|
+
};
|
|
409
|
+
return resolve ? (ref) => resolve(ref) ?? local(ref) : local;
|
|
410
|
+
}
|
|
89
411
|
function layoutScopes(schema) {
|
|
90
412
|
const scopes = [{ path: [], label: "Form (root)" }];
|
|
91
|
-
const walk = (nodes) => {
|
|
413
|
+
const walk = (nodes, labelPrefix) => {
|
|
92
414
|
for (const node of nodes) {
|
|
93
|
-
|
|
415
|
+
const allProps = node.path.every((s) => s.kind === "prop");
|
|
416
|
+
if (allProps && (node.kind === "object" || node.kind === "array")) {
|
|
417
|
+
const labels = [...labelPrefix, node.key];
|
|
94
418
|
const suffix = node.kind === "array" ? " (items)" : "";
|
|
95
419
|
scopes.push({
|
|
96
420
|
path: node.path,
|
|
97
|
-
label:
|
|
421
|
+
label: labels.join(" \u203A ") + suffix
|
|
98
422
|
});
|
|
99
|
-
walk(node.children);
|
|
423
|
+
walk(node.children, labels);
|
|
100
424
|
}
|
|
101
425
|
}
|
|
102
426
|
};
|
|
103
|
-
walk(buildPropertyTree(schema));
|
|
427
|
+
walk(buildPropertyTree(schema), []);
|
|
104
428
|
return scopes;
|
|
105
429
|
}
|
|
106
430
|
|
|
107
431
|
// src/model/mutate.ts
|
|
432
|
+
function segKey(seg) {
|
|
433
|
+
return seg && seg.kind === "prop" ? seg.key : void 0;
|
|
434
|
+
}
|
|
435
|
+
function propKeys(path) {
|
|
436
|
+
return path.flatMap((s) => s.kind === "prop" ? [s.key] : []);
|
|
437
|
+
}
|
|
108
438
|
function clone(v) {
|
|
109
439
|
return structuredClone(v);
|
|
110
440
|
}
|
|
@@ -114,10 +444,22 @@ function newProperty(kind) {
|
|
|
114
444
|
return { type: "object", properties: {} };
|
|
115
445
|
case "array":
|
|
116
446
|
return { type: "array", items: { type: "object", properties: {} } };
|
|
447
|
+
case "oneOf":
|
|
448
|
+
return {
|
|
449
|
+
type: "object",
|
|
450
|
+
discriminator: { propertyName: "kind" },
|
|
451
|
+
oneOf: [newBranch("option_1"), newBranch("option_2")]
|
|
452
|
+
};
|
|
117
453
|
default:
|
|
118
454
|
return { type: "string" };
|
|
119
455
|
}
|
|
120
456
|
}
|
|
457
|
+
function newBranch(value) {
|
|
458
|
+
return {
|
|
459
|
+
title: value,
|
|
460
|
+
properties: { kind: { const: value } }
|
|
461
|
+
};
|
|
462
|
+
}
|
|
121
463
|
function resolveContainer(schema, parentPath) {
|
|
122
464
|
if (parentPath.length === 0) {
|
|
123
465
|
return {
|
|
@@ -136,9 +478,11 @@ function resolveContainer(schema, parentPath) {
|
|
|
136
478
|
}
|
|
137
479
|
const owner = getProperty(schema, parentPath);
|
|
138
480
|
if (!owner) return null;
|
|
139
|
-
|
|
140
|
-
if (!properties) return null;
|
|
481
|
+
if (owner.oneOf) return null;
|
|
141
482
|
const target = owner.type === "array" ? owner.items : owner;
|
|
483
|
+
if (!target) return null;
|
|
484
|
+
if (!target.properties) target.properties = {};
|
|
485
|
+
const properties = target.properties;
|
|
142
486
|
return {
|
|
143
487
|
properties,
|
|
144
488
|
getOrder: () => target["x-ui-order"],
|
|
@@ -169,14 +513,135 @@ function addProperty(model, parentPath, kind) {
|
|
|
169
513
|
if (order) container.setOrder([...order, key]);
|
|
170
514
|
return {
|
|
171
515
|
model: { schema, uiSchema: model.uiSchema },
|
|
172
|
-
path: [...parentPath, key]
|
|
516
|
+
path: [...parentPath, prop(key)]
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
function uniqueBranchValue(oneOf) {
|
|
520
|
+
const existing = new Set(oneOf.map((b) => b.title).filter(Boolean));
|
|
521
|
+
let i = 1;
|
|
522
|
+
while (existing.has(`option_${i}`)) i++;
|
|
523
|
+
return `option_${i}`;
|
|
524
|
+
}
|
|
525
|
+
function addBranch(model, path) {
|
|
526
|
+
const schema = clone(model.schema);
|
|
527
|
+
const owner = getProperty(schema, path);
|
|
528
|
+
if (!owner || !owner.oneOf) return { model, path };
|
|
529
|
+
const index = owner.oneOf.length;
|
|
530
|
+
owner.oneOf.push(newBranch(uniqueBranchValue(owner.oneOf)));
|
|
531
|
+
return {
|
|
532
|
+
model: { schema, uiSchema: model.uiSchema },
|
|
533
|
+
path: [...path, branch(index)]
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
function removeBranch(model, path) {
|
|
537
|
+
const last = path[path.length - 1];
|
|
538
|
+
if (!last || last.kind !== "branch") return model;
|
|
539
|
+
const schema = clone(model.schema);
|
|
540
|
+
const owner = getProperty(schema, path.slice(0, -1));
|
|
541
|
+
if (!owner?.oneOf) return model;
|
|
542
|
+
owner.oneOf.splice(last.index, 1);
|
|
543
|
+
return { schema, uiSchema: model.uiSchema };
|
|
544
|
+
}
|
|
545
|
+
function moveBranch(model, path, direction) {
|
|
546
|
+
const last = path[path.length - 1];
|
|
547
|
+
if (!last || last.kind !== "branch") return model;
|
|
548
|
+
const schema = clone(model.schema);
|
|
549
|
+
const owner = getProperty(schema, path.slice(0, -1));
|
|
550
|
+
if (!owner?.oneOf) return model;
|
|
551
|
+
const from = last.index;
|
|
552
|
+
const to = from + direction;
|
|
553
|
+
if (to < 0 || to >= owner.oneOf.length) return model;
|
|
554
|
+
const [moved] = owner.oneOf.splice(from, 1);
|
|
555
|
+
owner.oneOf.splice(to, 0, moved);
|
|
556
|
+
return { schema, uiSchema: model.uiSchema };
|
|
557
|
+
}
|
|
558
|
+
function newCondition() {
|
|
559
|
+
return { if: { properties: {} }, then: { properties: {} } };
|
|
560
|
+
}
|
|
561
|
+
function addConditional(model, objectPath, combinator = "allOf") {
|
|
562
|
+
const schema = clone(model.schema);
|
|
563
|
+
const owner = conditionalOwner(schema, objectPath);
|
|
564
|
+
if (!owner) return { model, path: objectPath };
|
|
565
|
+
if (!Array.isArray(owner[combinator])) owner[combinator] = [];
|
|
566
|
+
const arr = owner[combinator];
|
|
567
|
+
const index = arr.length;
|
|
568
|
+
arr.push(newCondition());
|
|
569
|
+
return {
|
|
570
|
+
model: { schema, uiSchema: model.uiSchema },
|
|
571
|
+
path: [...objectPath, condition(index, combinator)]
|
|
173
572
|
};
|
|
174
573
|
}
|
|
574
|
+
function conditionalOwner(schema, objectPath) {
|
|
575
|
+
if (objectPath.length === 0) return schema;
|
|
576
|
+
const owner = getProperty(schema, objectPath);
|
|
577
|
+
if (!owner) return null;
|
|
578
|
+
const objectLike = owner.type === "object" || owner.type === void 0;
|
|
579
|
+
if (!objectLike || owner.oneOf) return null;
|
|
580
|
+
return owner;
|
|
581
|
+
}
|
|
582
|
+
function removeConditional(model, path) {
|
|
583
|
+
const last = path[path.length - 1];
|
|
584
|
+
if (!last || last.kind !== "condition") return model;
|
|
585
|
+
const schema = clone(model.schema);
|
|
586
|
+
const owner = conditionalOwner(schema, path.slice(0, -1));
|
|
587
|
+
const arr = owner?.[last.combinator];
|
|
588
|
+
if (!arr) return model;
|
|
589
|
+
arr.splice(last.index, 1);
|
|
590
|
+
if (arr.length === 0) delete owner[last.combinator];
|
|
591
|
+
return { schema, uiSchema: model.uiSchema };
|
|
592
|
+
}
|
|
593
|
+
function setCondition(model, path, ifSchema) {
|
|
594
|
+
const last = path[path.length - 1];
|
|
595
|
+
if (!last || last.kind !== "condition") return model;
|
|
596
|
+
const schema = clone(model.schema);
|
|
597
|
+
const member = getProperty(schema, path);
|
|
598
|
+
if (!member) return model;
|
|
599
|
+
member.if = ifSchema;
|
|
600
|
+
if (!member.then) member.then = { properties: {} };
|
|
601
|
+
return { schema, uiSchema: model.uiSchema };
|
|
602
|
+
}
|
|
603
|
+
function addElse(model, path) {
|
|
604
|
+
const last = path[path.length - 1];
|
|
605
|
+
if (!last || last.kind !== "condition") return model;
|
|
606
|
+
const schema = clone(model.schema);
|
|
607
|
+
const member = getProperty(schema, path);
|
|
608
|
+
if (!member || member.else) return model;
|
|
609
|
+
member.else = { properties: {} };
|
|
610
|
+
return { schema, uiSchema: model.uiSchema };
|
|
611
|
+
}
|
|
612
|
+
function removeElse(model, path) {
|
|
613
|
+
const last = path[path.length - 1];
|
|
614
|
+
if (!last || last.kind !== "condition") return model;
|
|
615
|
+
const schema = clone(model.schema);
|
|
616
|
+
const member = getProperty(schema, path);
|
|
617
|
+
if (!member || !member.else) return model;
|
|
618
|
+
delete member.else;
|
|
619
|
+
return { schema, uiSchema: model.uiSchema };
|
|
620
|
+
}
|
|
621
|
+
function otherCombinator(c) {
|
|
622
|
+
return c === "allOf" ? "anyOf" : "allOf";
|
|
623
|
+
}
|
|
624
|
+
function setConditionalBehavior(model, objectPath, next) {
|
|
625
|
+
const schema = clone(model.schema);
|
|
626
|
+
const owner = conditionalOwner(schema, objectPath);
|
|
627
|
+
if (!owner) return model;
|
|
628
|
+
const from = otherCombinator(next);
|
|
629
|
+
const source = owner[from];
|
|
630
|
+
if (!Array.isArray(source)) return model;
|
|
631
|
+
const conditionals = source.filter((m) => !!m.if);
|
|
632
|
+
if (conditionals.length === 0) return model;
|
|
633
|
+
const rest = source.filter((m) => !m.if);
|
|
634
|
+
if (rest.length > 0) owner[from] = rest;
|
|
635
|
+
else delete owner[from];
|
|
636
|
+
owner[next] = [...owner[next] ?? [], ...conditionals];
|
|
637
|
+
return { schema, uiSchema: model.uiSchema };
|
|
638
|
+
}
|
|
175
639
|
function deleteProperty(model, path) {
|
|
176
640
|
if (path.length === 0) return model;
|
|
177
641
|
const schema = clone(model.schema);
|
|
178
642
|
const parentPath = path.slice(0, -1);
|
|
179
|
-
const key = path[path.length - 1];
|
|
643
|
+
const key = segKey(path[path.length - 1]);
|
|
644
|
+
if (key === void 0) return model;
|
|
180
645
|
const container = resolveContainer(schema, parentPath);
|
|
181
646
|
if (!container || !(key in container.properties)) return model;
|
|
182
647
|
delete container.properties[key];
|
|
@@ -206,32 +671,38 @@ function renameUiEntry(uiSchema, parentPath, prevKey, nextKey) {
|
|
|
206
671
|
delete next[prevKey];
|
|
207
672
|
return next;
|
|
208
673
|
}
|
|
674
|
+
function updateContainerRefs(container, prevKey, nextKey) {
|
|
675
|
+
const rename = (k) => k === prevKey ? nextKey : k;
|
|
676
|
+
const order = container.getOrder();
|
|
677
|
+
if (order) container.setOrder(order.map(rename));
|
|
678
|
+
const required = container.getRequired();
|
|
679
|
+
if (required) container.setRequired(required.map(rename));
|
|
680
|
+
}
|
|
209
681
|
function renameProperty(model, path, nextKey) {
|
|
210
682
|
if (path.length === 0) return { model, path };
|
|
211
683
|
const parentPath = path.slice(0, -1);
|
|
212
|
-
const prevKey = path[path.length - 1];
|
|
213
|
-
if (
|
|
684
|
+
const prevKey = segKey(path[path.length - 1]);
|
|
685
|
+
if (prevKey === void 0) return { model, path };
|
|
686
|
+
if (nextKey === prevKey || !nextKey || isReservedKey(nextKey))
|
|
687
|
+
return { model, path };
|
|
214
688
|
const schema = clone(model.schema);
|
|
215
689
|
const container = resolveContainer(schema, parentPath);
|
|
216
690
|
if (!container || !(prevKey in container.properties)) return { model, path };
|
|
217
691
|
if (nextKey in container.properties) return { model, path };
|
|
218
692
|
renameKeyInPlace(container.properties, prevKey, nextKey);
|
|
219
|
-
|
|
220
|
-
const order = container.getOrder();
|
|
221
|
-
if (order) container.setOrder(order.map(rename));
|
|
222
|
-
const required = container.getRequired();
|
|
223
|
-
if (required) container.setRequired(required.map(rename));
|
|
693
|
+
updateContainerRefs(container, prevKey, nextKey);
|
|
224
694
|
const uiSchema = renameUiEntry(model.uiSchema, parentPath, prevKey, nextKey);
|
|
225
695
|
return {
|
|
226
696
|
model: { schema, uiSchema },
|
|
227
|
-
path: [...parentPath, nextKey]
|
|
697
|
+
path: [...parentPath, prop(nextKey)]
|
|
228
698
|
};
|
|
229
699
|
}
|
|
230
700
|
function moveProperty(model, path, direction) {
|
|
231
701
|
if (path.length === 0) return model;
|
|
232
702
|
const schema = clone(model.schema);
|
|
233
703
|
const parentPath = path.slice(0, -1);
|
|
234
|
-
const key = path[path.length - 1];
|
|
704
|
+
const key = segKey(path[path.length - 1]);
|
|
705
|
+
if (key === void 0) return model;
|
|
235
706
|
const container = resolveContainer(schema, parentPath);
|
|
236
707
|
if (!container) return model;
|
|
237
708
|
const keys = Object.keys(container.properties);
|
|
@@ -249,7 +720,8 @@ function setRequired(model, path, required) {
|
|
|
249
720
|
if (path.length === 0) return model;
|
|
250
721
|
const schema = clone(model.schema);
|
|
251
722
|
const parentPath = path.slice(0, -1);
|
|
252
|
-
const key = path[path.length - 1];
|
|
723
|
+
const key = segKey(path[path.length - 1]);
|
|
724
|
+
if (key === void 0) return model;
|
|
253
725
|
const container = resolveContainer(schema, parentPath);
|
|
254
726
|
if (!container) return model;
|
|
255
727
|
const current = new Set(container.getRequired() ?? []);
|
|
@@ -261,32 +733,159 @@ function setRequired(model, path, required) {
|
|
|
261
733
|
function updateProperty(model, path, patch) {
|
|
262
734
|
if (path.length === 0) return model;
|
|
263
735
|
const schema = clone(model.schema);
|
|
264
|
-
const
|
|
265
|
-
if (!
|
|
266
|
-
Object.
|
|
736
|
+
const prop2 = getProperty(schema, path);
|
|
737
|
+
if (!prop2) return model;
|
|
738
|
+
for (const [k, v] of Object.entries(patch)) {
|
|
739
|
+
if (v === void 0) delete prop2[k];
|
|
740
|
+
else prop2[k] = v;
|
|
741
|
+
}
|
|
742
|
+
return { schema, uiSchema: model.uiSchema };
|
|
743
|
+
}
|
|
744
|
+
function currentTypeChoice(prop2) {
|
|
745
|
+
return prop2.oneOf ? "oneOf" : prop2.type ?? "string";
|
|
746
|
+
}
|
|
747
|
+
function changePropertyType(model, path, next) {
|
|
748
|
+
if (path.length === 0 || !next) return model;
|
|
749
|
+
const schema = clone(model.schema);
|
|
750
|
+
const prop2 = getProperty(schema, path);
|
|
751
|
+
if (!prop2 || !prop2.$ref && currentTypeChoice(prop2) === next) return model;
|
|
752
|
+
delete prop2.properties;
|
|
753
|
+
delete prop2.items;
|
|
754
|
+
delete prop2.enum;
|
|
755
|
+
delete prop2.oneOf;
|
|
756
|
+
delete prop2.discriminator;
|
|
757
|
+
delete prop2.const;
|
|
758
|
+
delete prop2.$ref;
|
|
759
|
+
if (next !== "object") {
|
|
760
|
+
delete prop2.allOf;
|
|
761
|
+
delete prop2.anyOf;
|
|
762
|
+
}
|
|
763
|
+
delete prop2.minLength;
|
|
764
|
+
delete prop2.maxLength;
|
|
765
|
+
delete prop2.pattern;
|
|
766
|
+
delete prop2.format;
|
|
767
|
+
delete prop2.minimum;
|
|
768
|
+
delete prop2.maximum;
|
|
769
|
+
delete prop2.minItems;
|
|
770
|
+
delete prop2.maxItems;
|
|
771
|
+
if (next === "oneOf") {
|
|
772
|
+
const seed = newProperty("oneOf");
|
|
773
|
+
prop2.type = seed.type;
|
|
774
|
+
prop2.discriminator = seed.discriminator;
|
|
775
|
+
prop2.oneOf = seed.oneOf;
|
|
776
|
+
} else {
|
|
777
|
+
prop2.type = next;
|
|
778
|
+
if (next === "object") prop2.properties = {};
|
|
779
|
+
if (next === "array") prop2.items = { type: "string" };
|
|
780
|
+
}
|
|
267
781
|
return { schema, uiSchema: model.uiSchema };
|
|
268
782
|
}
|
|
269
|
-
function
|
|
270
|
-
|
|
783
|
+
function defPointer(name) {
|
|
784
|
+
return `#/$defs/${name}`;
|
|
785
|
+
}
|
|
786
|
+
function setReference(model, path, name) {
|
|
787
|
+
if (path.length === 0 || !name) return model;
|
|
788
|
+
const schema = clone(model.schema);
|
|
789
|
+
const prop2 = getProperty(schema, path);
|
|
790
|
+
if (!prop2) return model;
|
|
791
|
+
const defs = schema.$defs ?? schema.definitions;
|
|
792
|
+
if (!defs || !(name in defs)) return model;
|
|
793
|
+
if (prop2.type === "array") {
|
|
794
|
+
prop2.items = { $ref: defPointer(name) };
|
|
795
|
+
return { schema, uiSchema: model.uiSchema };
|
|
796
|
+
}
|
|
797
|
+
makeReference(prop2, name);
|
|
798
|
+
return { schema, uiSchema: model.uiSchema };
|
|
799
|
+
}
|
|
800
|
+
function makeReference(prop2, name) {
|
|
801
|
+
delete prop2.properties;
|
|
802
|
+
delete prop2.items;
|
|
803
|
+
delete prop2.enum;
|
|
804
|
+
delete prop2.oneOf;
|
|
805
|
+
delete prop2.discriminator;
|
|
806
|
+
delete prop2.const;
|
|
807
|
+
delete prop2.allOf;
|
|
808
|
+
delete prop2.anyOf;
|
|
809
|
+
delete prop2.type;
|
|
810
|
+
delete prop2.minLength;
|
|
811
|
+
delete prop2.maxLength;
|
|
812
|
+
delete prop2.pattern;
|
|
813
|
+
delete prop2.format;
|
|
814
|
+
delete prop2.minimum;
|
|
815
|
+
delete prop2.maximum;
|
|
816
|
+
delete prop2.minItems;
|
|
817
|
+
delete prop2.maxItems;
|
|
818
|
+
prop2.$ref = defPointer(name);
|
|
819
|
+
}
|
|
820
|
+
function walkSchemaNodes(node, visitor, parentKey) {
|
|
821
|
+
if (Array.isArray(node)) {
|
|
822
|
+
for (const item of node) walkSchemaNodes(item, visitor, parentKey);
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
if (!node || typeof node !== "object") return;
|
|
826
|
+
const obj = node;
|
|
827
|
+
visitor(obj, parentKey);
|
|
828
|
+
for (const key of Object.keys(obj)) {
|
|
829
|
+
walkSchemaNodes(obj[key], visitor, key);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
function rewriteRefs(node, from, to) {
|
|
833
|
+
walkSchemaNodes(node, (obj, key) => {
|
|
834
|
+
if (key !== "$ref" && obj.$ref === from) obj.$ref = to;
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
function countRefUsages(schema, name) {
|
|
838
|
+
let count = 0;
|
|
839
|
+
const pointers = /* @__PURE__ */ new Set([`#/$defs/${name}`, `#/definitions/${name}`]);
|
|
840
|
+
walkSchemaNodes(schema, (obj) => {
|
|
841
|
+
if (typeof obj.$ref === "string" && pointers.has(obj.$ref)) count++;
|
|
842
|
+
});
|
|
843
|
+
return count;
|
|
844
|
+
}
|
|
845
|
+
function addDefinition(model, name) {
|
|
846
|
+
const schema = clone(model.schema);
|
|
847
|
+
if (!schema.$defs) schema.$defs = {};
|
|
848
|
+
const key = uniqueKey(name ?? "Definition", schema.$defs);
|
|
849
|
+
schema.$defs[key] = { type: "object", properties: {} };
|
|
850
|
+
return {
|
|
851
|
+
model: { schema, uiSchema: model.uiSchema },
|
|
852
|
+
path: [def(key)]
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
function renameDefinition(model, oldName, newName) {
|
|
856
|
+
const path = [def(oldName)];
|
|
857
|
+
if (!newName || newName === oldName || isReservedKey(newName))
|
|
858
|
+
return { model, path };
|
|
859
|
+
const schema = clone(model.schema);
|
|
860
|
+
const defs = schema.$defs ?? schema.definitions;
|
|
861
|
+
if (!defs || !(oldName in defs) || newName in defs) return { model, path };
|
|
862
|
+
renameKeyInPlace(defs, oldName, newName);
|
|
863
|
+
rewriteRefs(schema, `#/$defs/${oldName}`, `#/$defs/${newName}`);
|
|
864
|
+
rewriteRefs(schema, `#/definitions/${oldName}`, `#/$defs/${newName}`);
|
|
865
|
+
return {
|
|
866
|
+
model: { schema, uiSchema: model.uiSchema },
|
|
867
|
+
path: [def(newName)]
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
function removeDefinition(model, name) {
|
|
271
871
|
const schema = clone(model.schema);
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
delete prop.maxItems;
|
|
285
|
-
prop.type = nextType;
|
|
286
|
-
if (nextType === "object") prop.properties = {};
|
|
287
|
-
if (nextType === "array") prop.items = { type: "string" };
|
|
872
|
+
const inDefs = schema.$defs && name in schema.$defs;
|
|
873
|
+
const inDefs7 = schema.definitions && name in schema.definitions;
|
|
874
|
+
if (!inDefs && !inDefs7) return model;
|
|
875
|
+
if (countRefUsages(schema, name) > 0) return model;
|
|
876
|
+
if (inDefs) {
|
|
877
|
+
delete schema.$defs[name];
|
|
878
|
+
if (Object.keys(schema.$defs).length === 0) delete schema.$defs;
|
|
879
|
+
} else {
|
|
880
|
+
delete schema.definitions[name];
|
|
881
|
+
if (Object.keys(schema.definitions).length === 0)
|
|
882
|
+
delete schema.definitions;
|
|
883
|
+
}
|
|
288
884
|
return { schema, uiSchema: model.uiSchema };
|
|
289
885
|
}
|
|
886
|
+
function definitionUsageCount(schema, name) {
|
|
887
|
+
return countRefUsages(schema, name);
|
|
888
|
+
}
|
|
290
889
|
function normalizeChoices(choices) {
|
|
291
890
|
const seen = /* @__PURE__ */ new Set();
|
|
292
891
|
const clean = [];
|
|
@@ -314,22 +913,22 @@ function withEnumNames(entry, choices) {
|
|
|
314
913
|
function setEnum(model, path, choices) {
|
|
315
914
|
if (path.length === 0) return model;
|
|
316
915
|
const schema = clone(model.schema);
|
|
317
|
-
const
|
|
318
|
-
if (!
|
|
916
|
+
const prop2 = getProperty(schema, path);
|
|
917
|
+
if (!prop2) return model;
|
|
319
918
|
const clean = normalizeChoices(choices);
|
|
320
|
-
if (clean.length === 0) delete
|
|
321
|
-
else
|
|
919
|
+
if (clean.length === 0) delete prop2.enum;
|
|
920
|
+
else prop2.enum = clean.map((c) => c.value);
|
|
322
921
|
const nextEntry = withEnumNames(
|
|
323
922
|
getUiEntryAtPath(model.uiSchema, path),
|
|
324
923
|
clean
|
|
325
924
|
);
|
|
326
|
-
const uiSchema = setEntryIn(model.uiSchema, path, nextEntry);
|
|
925
|
+
const uiSchema = setEntryIn(model.uiSchema, propKeys(path), nextEntry);
|
|
327
926
|
return { schema, uiSchema };
|
|
328
927
|
}
|
|
329
|
-
function readEnumChoices(
|
|
330
|
-
if (!Array.isArray(
|
|
928
|
+
function readEnumChoices(prop2, uiEntry) {
|
|
929
|
+
if (!Array.isArray(prop2.enum)) return [];
|
|
331
930
|
const enumNames = uiEntry?.["x-ui-options"]?.enumNames ?? {};
|
|
332
|
-
return
|
|
931
|
+
return prop2.enum.map((v) => {
|
|
333
932
|
const value = String(v);
|
|
334
933
|
return { value, label: enumNames[value] };
|
|
335
934
|
});
|
|
@@ -337,40 +936,41 @@ function readEnumChoices(prop, uiEntry) {
|
|
|
337
936
|
function setTableHints(model, path, hints) {
|
|
338
937
|
if (path.length === 0) return model;
|
|
339
938
|
const schema = clone(model.schema);
|
|
340
|
-
const
|
|
341
|
-
if (!
|
|
342
|
-
if (hints.cell)
|
|
343
|
-
else delete
|
|
939
|
+
const prop2 = getProperty(schema, path);
|
|
940
|
+
if (!prop2) return model;
|
|
941
|
+
if (hints.cell) prop2["x-ui-cell"] = hints.cell;
|
|
942
|
+
else delete prop2["x-ui-cell"];
|
|
344
943
|
if (hints.cellOptions && Object.keys(hints.cellOptions).length > 0) {
|
|
345
|
-
|
|
944
|
+
prop2["x-ui-cell-options"] = hints.cellOptions;
|
|
346
945
|
} else {
|
|
347
|
-
delete
|
|
946
|
+
delete prop2["x-ui-cell-options"];
|
|
348
947
|
}
|
|
349
948
|
if (hints.column && Object.keys(hints.column).length > 0) {
|
|
350
|
-
|
|
949
|
+
prop2["x-ui-column"] = hints.column;
|
|
351
950
|
} else {
|
|
352
|
-
delete
|
|
951
|
+
delete prop2["x-ui-column"];
|
|
353
952
|
}
|
|
354
953
|
return { schema, uiSchema: model.uiSchema };
|
|
355
954
|
}
|
|
356
|
-
function readTableHints(
|
|
955
|
+
function readTableHints(prop2) {
|
|
357
956
|
return {
|
|
358
|
-
cell: typeof
|
|
359
|
-
cellOptions:
|
|
360
|
-
column:
|
|
957
|
+
cell: typeof prop2["x-ui-cell"] === "string" ? prop2["x-ui-cell"] : void 0,
|
|
958
|
+
cellOptions: prop2["x-ui-cell-options"],
|
|
959
|
+
column: prop2["x-ui-column"]
|
|
361
960
|
};
|
|
362
961
|
}
|
|
363
962
|
function getUiEntryAtPath(uiSchema, path) {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
963
|
+
const keys = propKeys(path);
|
|
964
|
+
if (keys.length === 0) return void 0;
|
|
965
|
+
let entry = uiSchema[keys[0]];
|
|
966
|
+
for (let i = 1; i < keys.length && entry; i++) {
|
|
367
967
|
const fields = entry["x-ui-fields"];
|
|
368
|
-
entry = fields?.[
|
|
968
|
+
entry = fields?.[keys[i]];
|
|
369
969
|
}
|
|
370
970
|
return entry;
|
|
371
971
|
}
|
|
372
|
-
function setEntryIn(map,
|
|
373
|
-
const [head, ...rest] =
|
|
972
|
+
function setEntryIn(map, keys, entry) {
|
|
973
|
+
const [head, ...rest] = keys;
|
|
374
974
|
const next = { ...map };
|
|
375
975
|
if (rest.length === 0) {
|
|
376
976
|
if (entry && Object.keys(entry).length > 0) next[head] = entry;
|
|
@@ -391,15 +991,15 @@ function setEntryIn(map, path, entry) {
|
|
|
391
991
|
}
|
|
392
992
|
function setUiEntry(model, path, entry) {
|
|
393
993
|
if (path.length === 0) return model;
|
|
394
|
-
const uiSchema = setEntryIn(model.uiSchema, path, entry);
|
|
994
|
+
const uiSchema = setEntryIn(model.uiSchema, propKeys(path), entry);
|
|
395
995
|
return { schema: model.schema, uiSchema };
|
|
396
996
|
}
|
|
397
997
|
function setPropertyLayout(model, path, layout) {
|
|
398
998
|
if (path.length === 0) return model;
|
|
399
999
|
const schema = clone(model.schema);
|
|
400
|
-
const
|
|
401
|
-
if (!
|
|
402
|
-
const target =
|
|
1000
|
+
const prop2 = getProperty(schema, path);
|
|
1001
|
+
if (!prop2) return model;
|
|
1002
|
+
const target = prop2.type === "array" ? prop2.items : prop2;
|
|
403
1003
|
if (!target) return model;
|
|
404
1004
|
if (layout) target["x-ui-layout"] = layout;
|
|
405
1005
|
else delete target["x-ui-layout"];
|
|
@@ -423,9 +1023,9 @@ function setRootLayoutRows(model, rows) {
|
|
|
423
1023
|
function setScopedLayoutRows(model, scopePath, rows) {
|
|
424
1024
|
if (scopePath.length === 0) return setRootLayoutRows(model, rows);
|
|
425
1025
|
const schema = clone(model.schema);
|
|
426
|
-
const
|
|
427
|
-
if (!
|
|
428
|
-
const target =
|
|
1026
|
+
const prop2 = getProperty(schema, scopePath);
|
|
1027
|
+
if (!prop2) return model;
|
|
1028
|
+
const target = prop2.type === "array" ? prop2.items : prop2;
|
|
429
1029
|
if (!target) return model;
|
|
430
1030
|
const layout = { ...target["x-ui-layout"] ?? {} };
|
|
431
1031
|
if (rows && rows.length > 0) layout.rows = rows;
|
|
@@ -438,8 +1038,8 @@ function getScopedLayout(schema, uiSchema, scopePath) {
|
|
|
438
1038
|
if (scopePath.length === 0) {
|
|
439
1039
|
return uiSchema["x-ui-layout"];
|
|
440
1040
|
}
|
|
441
|
-
const
|
|
442
|
-
const target =
|
|
1041
|
+
const prop2 = getProperty(schema, scopePath);
|
|
1042
|
+
const target = prop2?.type === "array" ? prop2.items : prop2;
|
|
443
1043
|
return target?.["x-ui-layout"];
|
|
444
1044
|
}
|
|
445
1045
|
function setScopedLayoutOptions(model, scopePath, options) {
|
|
@@ -455,6 +1055,120 @@ function setScopedLayoutOptions(model, scopePath, options) {
|
|
|
455
1055
|
if (scopePath.length === 0) return setRootLayout(model, layout);
|
|
456
1056
|
return setPropertyLayout(model, scopePath, layout);
|
|
457
1057
|
}
|
|
1058
|
+
function setScopedLayoutGroups(model, scopePath, groups) {
|
|
1059
|
+
const current = getScopedLayout(model.schema, model.uiSchema, scopePath) ?? {};
|
|
1060
|
+
const merged = { ...current };
|
|
1061
|
+
if (groups && groups.length > 0) {
|
|
1062
|
+
merged.groups = groups;
|
|
1063
|
+
delete merged.rows;
|
|
1064
|
+
} else {
|
|
1065
|
+
delete merged.groups;
|
|
1066
|
+
}
|
|
1067
|
+
const layout = Object.keys(merged).length > 0 ? merged : void 0;
|
|
1068
|
+
if (scopePath.length === 0) return setRootLayout(model, layout);
|
|
1069
|
+
return setPropertyLayout(model, scopePath, layout);
|
|
1070
|
+
}
|
|
1071
|
+
function groupsOf(model, scopePath) {
|
|
1072
|
+
return getScopedLayout(model.schema, model.uiSchema, scopePath)?.groups ?? [];
|
|
1073
|
+
}
|
|
1074
|
+
function addLayoutGroup(model, scopePath, label) {
|
|
1075
|
+
const groups = groupsOf(model, scopePath);
|
|
1076
|
+
const next = { label: label ?? `Group ${groups.length + 1}`, rows: [] };
|
|
1077
|
+
return setScopedLayoutGroups(model, scopePath, [...groups, next]);
|
|
1078
|
+
}
|
|
1079
|
+
function renameLayoutGroup(model, scopePath, groupIndex, label) {
|
|
1080
|
+
const groups = groupsOf(model, scopePath);
|
|
1081
|
+
if (!groups[groupIndex] || !label.trim()) return model;
|
|
1082
|
+
return setScopedLayoutGroups(
|
|
1083
|
+
model,
|
|
1084
|
+
scopePath,
|
|
1085
|
+
groups.map((group, i) => i === groupIndex ? { ...group, label } : group)
|
|
1086
|
+
);
|
|
1087
|
+
}
|
|
1088
|
+
function placeFieldInGroup(model, scopePath, key, target) {
|
|
1089
|
+
const groups = groupsOf(model, scopePath);
|
|
1090
|
+
if (!key || groups.length === 0) return model;
|
|
1091
|
+
const stripped = groups.map((group) => ({
|
|
1092
|
+
...group,
|
|
1093
|
+
rows: group.rows.map((row2) => ({
|
|
1094
|
+
...row2,
|
|
1095
|
+
fields: row2.fields.filter((f) => f !== key)
|
|
1096
|
+
}))
|
|
1097
|
+
}));
|
|
1098
|
+
if (!target) return setScopedLayoutGroups(model, scopePath, stripped);
|
|
1099
|
+
const { groupIndex, rowIndex } = target;
|
|
1100
|
+
const row = stripped[groupIndex]?.rows[rowIndex];
|
|
1101
|
+
if (!row) return setScopedLayoutGroups(model, scopePath, stripped);
|
|
1102
|
+
stripped[groupIndex].rows = stripped[groupIndex].rows.map(
|
|
1103
|
+
(r, i) => i === rowIndex ? { ...r, fields: [...r.fields, key] } : r
|
|
1104
|
+
);
|
|
1105
|
+
return setScopedLayoutGroups(model, scopePath, stripped);
|
|
1106
|
+
}
|
|
1107
|
+
function moveLayoutGroup(model, scopePath, groupIndex, direction) {
|
|
1108
|
+
const groups = groupsOf(model, scopePath);
|
|
1109
|
+
const target = groupIndex + direction;
|
|
1110
|
+
if (!groups[groupIndex] || target < 0 || target >= groups.length)
|
|
1111
|
+
return model;
|
|
1112
|
+
const next = [...groups];
|
|
1113
|
+
[next[groupIndex], next[target]] = [next[target], next[groupIndex]];
|
|
1114
|
+
return setScopedLayoutGroups(model, scopePath, next);
|
|
1115
|
+
}
|
|
1116
|
+
function setLayoutGroupRule(model, scopePath, groupIndex, rule) {
|
|
1117
|
+
const groups = groupsOf(model, scopePath);
|
|
1118
|
+
if (!groups[groupIndex]) return model;
|
|
1119
|
+
const next = groups.map((group, i) => {
|
|
1120
|
+
if (i !== groupIndex) return group;
|
|
1121
|
+
const { rule: _drop, ...rest } = group;
|
|
1122
|
+
return rule ? { ...rest, rule } : rest;
|
|
1123
|
+
});
|
|
1124
|
+
return setScopedLayoutGroups(model, scopePath, next);
|
|
1125
|
+
}
|
|
1126
|
+
function setScopedLayoutVariant(model, scopePath, variant) {
|
|
1127
|
+
const current = getScopedLayout(model.schema, model.uiSchema, scopePath) ?? {};
|
|
1128
|
+
const merged = { ...current };
|
|
1129
|
+
if (variant && variant !== "groups") merged.variant = variant;
|
|
1130
|
+
else delete merged.variant;
|
|
1131
|
+
const layout = Object.keys(merged).length > 0 ? merged : void 0;
|
|
1132
|
+
if (scopePath.length === 0) return setRootLayout(model, layout);
|
|
1133
|
+
return setPropertyLayout(model, scopePath, layout);
|
|
1134
|
+
}
|
|
1135
|
+
function removeLayoutGroup(model, scopePath, groupIndex) {
|
|
1136
|
+
const groups = groupsOf(model, scopePath);
|
|
1137
|
+
if (!groups[groupIndex]) return model;
|
|
1138
|
+
const remaining = groups.filter((_, i) => i !== groupIndex);
|
|
1139
|
+
if (remaining.length > 0) {
|
|
1140
|
+
return setScopedLayoutGroups(model, scopePath, remaining);
|
|
1141
|
+
}
|
|
1142
|
+
const cleared = setScopedLayoutGroups(model, scopePath, []);
|
|
1143
|
+
return setScopedLayoutRows(cleared, scopePath, groups[groupIndex].rows);
|
|
1144
|
+
}
|
|
1145
|
+
function setScopedGroupRows(model, scopePath, groupIndex, rows) {
|
|
1146
|
+
const groups = getScopedLayout(
|
|
1147
|
+
model.schema,
|
|
1148
|
+
model.uiSchema,
|
|
1149
|
+
scopePath
|
|
1150
|
+
)?.groups;
|
|
1151
|
+
if (!groups?.[groupIndex]) return model;
|
|
1152
|
+
const next = groups.map(
|
|
1153
|
+
(group, i) => i === groupIndex ? { ...group, rows } : group
|
|
1154
|
+
);
|
|
1155
|
+
return setScopedLayoutGroups(model, scopePath, next);
|
|
1156
|
+
}
|
|
1157
|
+
function setScopedLayoutMode(model, scopePath, mode) {
|
|
1158
|
+
const layout = getScopedLayout(model.schema, model.uiSchema, scopePath) ?? {};
|
|
1159
|
+
const groups = layout.groups ?? [];
|
|
1160
|
+
const rows = layout.rows ?? [];
|
|
1161
|
+
if (mode === "groups") {
|
|
1162
|
+
if (groups.length > 0) return model;
|
|
1163
|
+
return setScopedLayoutGroups(model, scopePath, [
|
|
1164
|
+
{ label: "Group 1", rows }
|
|
1165
|
+
]);
|
|
1166
|
+
}
|
|
1167
|
+
if (groups.length === 0) return model;
|
|
1168
|
+
const flattened = [...rows, ...groups.flatMap((group) => group.rows)];
|
|
1169
|
+
const cleared = setScopedLayoutGroups(model, scopePath, []);
|
|
1170
|
+
return setScopedLayoutRows(cleared, scopePath, flattened);
|
|
1171
|
+
}
|
|
458
1172
|
function setGlobalOptions(model, options) {
|
|
459
1173
|
const uiSchema = { ...model.uiSchema };
|
|
460
1174
|
if (options) uiSchema["x-ui-globalOptions"] = options;
|
|
@@ -468,6 +1182,9 @@ var STRING_WIDGETS = [
|
|
|
468
1182
|
{ value: "text", label: "Text input" },
|
|
469
1183
|
{ value: "textarea", label: "Textarea" },
|
|
470
1184
|
{ value: "select", label: "Select" },
|
|
1185
|
+
{ value: "date", label: "Date" },
|
|
1186
|
+
{ value: "date-time", label: "Date & time" },
|
|
1187
|
+
{ value: "time", label: "Time" },
|
|
471
1188
|
{ value: "tags", label: "Tags" },
|
|
472
1189
|
{ value: "entity-select", label: "Entity select" },
|
|
473
1190
|
{ value: "json-editor", label: "JSON editor" }
|
|
@@ -489,8 +1206,15 @@ var ARRAY_WIDGETS = [
|
|
|
489
1206
|
{ value: "select", label: "Multi-select" },
|
|
490
1207
|
{ value: "tags", label: "Tags" }
|
|
491
1208
|
];
|
|
492
|
-
|
|
493
|
-
|
|
1209
|
+
var NULL_WIDGETS = [{ value: "display", label: "Display" }];
|
|
1210
|
+
var ONEOF_WIDGETS = [
|
|
1211
|
+
{ value: "one-of", label: "One of (variant)" }
|
|
1212
|
+
];
|
|
1213
|
+
function compatibleWidgets(prop2) {
|
|
1214
|
+
if (Array.isArray(prop2.oneOf) && prop2.oneOf.length > 0) {
|
|
1215
|
+
return [AUTO, ...ONEOF_WIDGETS];
|
|
1216
|
+
}
|
|
1217
|
+
switch (prop2.type) {
|
|
494
1218
|
case "string":
|
|
495
1219
|
return [AUTO, ...STRING_WIDGETS];
|
|
496
1220
|
case "number":
|
|
@@ -502,6 +1226,8 @@ function compatibleWidgets(prop) {
|
|
|
502
1226
|
return [AUTO, ...OBJECT_WIDGETS];
|
|
503
1227
|
case "array":
|
|
504
1228
|
return [AUTO, ...ARRAY_WIDGETS];
|
|
1229
|
+
case "null":
|
|
1230
|
+
return [AUTO, ...NULL_WIDGETS];
|
|
505
1231
|
default:
|
|
506
1232
|
return [AUTO];
|
|
507
1233
|
}
|
|
@@ -518,11 +1244,11 @@ var EMBEDDABLE_KEYS = [
|
|
|
518
1244
|
function embedUiSchema(schema, uiSchema) {
|
|
519
1245
|
const next = structuredClone(schema);
|
|
520
1246
|
for (const [fieldName, entry] of Object.entries(uiSchema)) {
|
|
521
|
-
const
|
|
522
|
-
if (!
|
|
1247
|
+
const prop2 = next.properties[fieldName];
|
|
1248
|
+
if (!prop2 || !entry) continue;
|
|
523
1249
|
for (const key of EMBEDDABLE_KEYS) {
|
|
524
1250
|
if (entry[key] !== void 0) {
|
|
525
|
-
|
|
1251
|
+
prop2[key] = entry[key];
|
|
526
1252
|
}
|
|
527
1253
|
}
|
|
528
1254
|
}
|
|
@@ -535,11 +1261,12 @@ import { useCallback, useMemo, useRef, useState } from "react";
|
|
|
535
1261
|
// src/capabilities.ts
|
|
536
1262
|
import { z } from "zod";
|
|
537
1263
|
import { warnLog } from "@petrarca/sonnet-core";
|
|
1264
|
+
var previewKindSchema = z.enum(["form", "table", "json", "capabilities"]);
|
|
538
1265
|
var capabilitiesSchema = z.object({
|
|
539
1266
|
mode: z.enum(["data", "full"]).default("full").meta({
|
|
540
1267
|
description: "What the designer authors. 'full' (default): JSON Schema + uiSchema (form presentation hints). 'data': JSON Schema only -- the UI Schema tab, Combined tab, and the inspector's per-field UI tab are hidden (pure data schemas: audit events, terminology)."
|
|
541
1268
|
}),
|
|
542
|
-
previews: z.array(
|
|
1269
|
+
previews: z.array(previewKindSchema).optional().meta({
|
|
543
1270
|
description: "Which tabs the center pane offers, in order. 'form'/'table'/'json' preview the schema; 'capabilities' is a JSON editor for this profile (opt-in, never in a default). When omitted, defaults to ['form','table','json'] in 'full' mode and ['json'] in 'data' mode. An empty list hides the center pane entirely."
|
|
544
1271
|
}),
|
|
545
1272
|
editablePaths: z.array(z.string()).optional().meta({
|
|
@@ -586,7 +1313,7 @@ function validateCapabilities(input) {
|
|
|
586
1313
|
}
|
|
587
1314
|
|
|
588
1315
|
// src/model/scoping.ts
|
|
589
|
-
function
|
|
1316
|
+
function escapeToken2(token) {
|
|
590
1317
|
return token.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
591
1318
|
}
|
|
592
1319
|
function parentContainerPointer(schema, path) {
|
|
@@ -595,20 +1322,27 @@ function parentContainerPointer(schema, path) {
|
|
|
595
1322
|
let pointer = "/properties";
|
|
596
1323
|
for (let i = 0; i < path.length; i++) {
|
|
597
1324
|
if (!container) return void 0;
|
|
598
|
-
const
|
|
599
|
-
|
|
600
|
-
|
|
1325
|
+
const segment = path[i];
|
|
1326
|
+
if (segment.kind !== "prop") return void 0;
|
|
1327
|
+
const key = segment.key;
|
|
1328
|
+
const prop2 = container[key];
|
|
1329
|
+
if (!prop2) return void 0;
|
|
601
1330
|
if (i === path.length - 1) return pointer;
|
|
602
|
-
const next = childContainer(
|
|
1331
|
+
const next = childContainer(prop2);
|
|
603
1332
|
if (!next) return void 0;
|
|
604
|
-
pointer =
|
|
1333
|
+
pointer = prop2.type === "array" ? `${pointer}/${escapeToken2(key)}/items/properties` : `${pointer}/${escapeToken2(key)}/properties`;
|
|
605
1334
|
container = next;
|
|
606
1335
|
}
|
|
607
1336
|
return pointer;
|
|
608
1337
|
}
|
|
609
1338
|
function isPropertyEditable(schema, path, editablePaths) {
|
|
610
1339
|
if (editablePaths.length === 0) return true;
|
|
611
|
-
const
|
|
1340
|
+
const allPropPrefix = path.slice(
|
|
1341
|
+
0,
|
|
1342
|
+
path.findIndex((s) => s.kind !== "prop") + 1 || path.length
|
|
1343
|
+
);
|
|
1344
|
+
const effectivePath = allPropPrefix.length < path.length ? allPropPrefix : path;
|
|
1345
|
+
const parent = parentContainerPointer(schema, effectivePath);
|
|
612
1346
|
if (parent === void 0) return false;
|
|
613
1347
|
return editablePaths.includes(parent);
|
|
614
1348
|
}
|
|
@@ -623,18 +1357,89 @@ function ownChildContainerPointer(schema, path) {
|
|
|
623
1357
|
if (parent === void 0) return void 0;
|
|
624
1358
|
let container = schema.properties;
|
|
625
1359
|
for (let i = 0; i < path.length; i++) {
|
|
626
|
-
const
|
|
627
|
-
if (
|
|
1360
|
+
const segment = path[i];
|
|
1361
|
+
if (segment.kind !== "prop") return void 0;
|
|
1362
|
+
const prop2 = container?.[segment.key];
|
|
1363
|
+
if (!prop2) return void 0;
|
|
628
1364
|
if (i === path.length - 1) {
|
|
629
|
-
const seg =
|
|
630
|
-
return
|
|
1365
|
+
const seg = escapeToken2(segment.key);
|
|
1366
|
+
return prop2.type === "array" ? `${parent}/${seg}/items/properties` : `${parent}/${seg}/properties`;
|
|
631
1367
|
}
|
|
632
|
-
container = childContainer(
|
|
1368
|
+
container = childContainer(prop2);
|
|
633
1369
|
}
|
|
634
1370
|
return void 0;
|
|
635
1371
|
}
|
|
636
1372
|
|
|
637
1373
|
// src/hooks/useDesignerState.ts
|
|
1374
|
+
function pathStartsWith(p, prefix) {
|
|
1375
|
+
if (p.length < prefix.length) return false;
|
|
1376
|
+
return prefix.every((seg, i) => sameSegment(seg, p[i]));
|
|
1377
|
+
}
|
|
1378
|
+
function sharesParentPrefix(p, removed) {
|
|
1379
|
+
if (p.length < removed.length) return false;
|
|
1380
|
+
const parentLen = removed.length - 1;
|
|
1381
|
+
for (let i = 0; i < parentLen; i++) {
|
|
1382
|
+
if (!sameSegment(p[i], removed[i])) return false;
|
|
1383
|
+
}
|
|
1384
|
+
return true;
|
|
1385
|
+
}
|
|
1386
|
+
function sameIndexedArray(a, b) {
|
|
1387
|
+
if (a.kind !== b.kind) return false;
|
|
1388
|
+
if (a.kind === "branch" && b.kind === "branch") return true;
|
|
1389
|
+
if (a.kind === "condition" && b.kind === "condition")
|
|
1390
|
+
return a.combinator === b.combinator;
|
|
1391
|
+
return false;
|
|
1392
|
+
}
|
|
1393
|
+
function affectedSibling(prev, removed, kind) {
|
|
1394
|
+
const last = removed[removed.length - 1];
|
|
1395
|
+
if (!last || last.kind !== kind) return null;
|
|
1396
|
+
if (!sharesParentPrefix(prev, removed)) return null;
|
|
1397
|
+
const at = removed.length - 1;
|
|
1398
|
+
const seg = prev[at];
|
|
1399
|
+
if (!seg || !sameIndexedArray(seg, last) || !("index" in seg)) return null;
|
|
1400
|
+
return { at, seg };
|
|
1401
|
+
}
|
|
1402
|
+
function adjustIndexedSelection(prev, removed, kind) {
|
|
1403
|
+
if (!prev) return null;
|
|
1404
|
+
const hit = affectedSibling(prev, removed, kind);
|
|
1405
|
+
if (!hit) return prev;
|
|
1406
|
+
const removedIndex = removed[removed.length - 1].index;
|
|
1407
|
+
if (hit.seg.index === removedIndex) return null;
|
|
1408
|
+
if (hit.seg.index < removedIndex) return prev;
|
|
1409
|
+
const shifted = { ...hit.seg, index: hit.seg.index - 1 };
|
|
1410
|
+
return [...prev.slice(0, hit.at), shifted, ...prev.slice(hit.at + 1)];
|
|
1411
|
+
}
|
|
1412
|
+
function adjustBranchSelection(prev, removed) {
|
|
1413
|
+
return adjustIndexedSelection(prev, removed, "branch");
|
|
1414
|
+
}
|
|
1415
|
+
function countConditionals(arr) {
|
|
1416
|
+
return Array.isArray(arr) ? arr.filter((m) => !!m.if).length : 0;
|
|
1417
|
+
}
|
|
1418
|
+
function selectedConditionInCombinator(prev, objectPath, from) {
|
|
1419
|
+
if (!prev) return null;
|
|
1420
|
+
const at = objectPath.length;
|
|
1421
|
+
const seg = prev[at];
|
|
1422
|
+
if (!seg || seg.kind !== "condition" || seg.combinator !== from) return null;
|
|
1423
|
+
if (!samePath(prev.slice(0, at), objectPath)) return null;
|
|
1424
|
+
return { at, index: seg.index };
|
|
1425
|
+
}
|
|
1426
|
+
function remapConditionSelection(before, prev, objectPath, next) {
|
|
1427
|
+
const from = next === "allOf" ? "anyOf" : "allOf";
|
|
1428
|
+
const hit = selectedConditionInCombinator(prev, objectPath, from);
|
|
1429
|
+
if (!hit || !prev) return prev;
|
|
1430
|
+
const owner = objectPath.length === 0 ? { allOf: before.schema.allOf, anyOf: before.schema.anyOf } : getProperty(before.schema, objectPath);
|
|
1431
|
+
const sourceArr = owner?.[from];
|
|
1432
|
+
if (!Array.isArray(sourceArr)) return prev;
|
|
1433
|
+
const posAmongSource = countConditionals(sourceArr.slice(0, hit.index));
|
|
1434
|
+
const targetOffset = countConditionals(
|
|
1435
|
+
owner?.[next]
|
|
1436
|
+
);
|
|
1437
|
+
return [
|
|
1438
|
+
...prev.slice(0, hit.at),
|
|
1439
|
+
condition(targetOffset + posAmongSource, next),
|
|
1440
|
+
...prev.slice(hit.at + 1)
|
|
1441
|
+
];
|
|
1442
|
+
}
|
|
638
1443
|
var EMPTY_SCHEMA = {
|
|
639
1444
|
title: "",
|
|
640
1445
|
type: "object",
|
|
@@ -695,6 +1500,56 @@ function useDesignerState(args) {
|
|
|
695
1500
|
commit(res.model);
|
|
696
1501
|
setSelection(res.path);
|
|
697
1502
|
},
|
|
1503
|
+
addBranch(path) {
|
|
1504
|
+
if (!canEditProperty(path)) return;
|
|
1505
|
+
const res = addBranch(modelRef.current, path);
|
|
1506
|
+
commit(res.model);
|
|
1507
|
+
setSelection(res.path);
|
|
1508
|
+
},
|
|
1509
|
+
removeBranch(path) {
|
|
1510
|
+
if (!canEditProperty(path)) return;
|
|
1511
|
+
commit(removeBranch(modelRef.current, path));
|
|
1512
|
+
setSelection((prev) => adjustBranchSelection(prev, path));
|
|
1513
|
+
},
|
|
1514
|
+
moveBranch(path, direction) {
|
|
1515
|
+
if (!canEditProperty(path)) return;
|
|
1516
|
+
commit(moveBranch(modelRef.current, path, direction));
|
|
1517
|
+
},
|
|
1518
|
+
addConditional(objectPath, combinator = "allOf") {
|
|
1519
|
+
if (!canAddChild(objectPath)) return;
|
|
1520
|
+
const res = addConditional(modelRef.current, objectPath, combinator);
|
|
1521
|
+
commit(res.model);
|
|
1522
|
+
setSelection(res.path);
|
|
1523
|
+
},
|
|
1524
|
+
setConditionalBehavior(objectPath, combinator) {
|
|
1525
|
+
if (!canAddChild(objectPath)) return;
|
|
1526
|
+
const before = modelRef.current;
|
|
1527
|
+
commit(setConditionalBehavior(before, objectPath, combinator));
|
|
1528
|
+
setSelection(
|
|
1529
|
+
(prev) => remapConditionSelection(before, prev, objectPath, combinator)
|
|
1530
|
+
);
|
|
1531
|
+
},
|
|
1532
|
+
removeConditional(path) {
|
|
1533
|
+
if (!canAddChild(path.slice(0, -1))) return;
|
|
1534
|
+
commit(removeConditional(modelRef.current, path));
|
|
1535
|
+
setSelection((prev) => adjustIndexedSelection(prev, path, "condition"));
|
|
1536
|
+
},
|
|
1537
|
+
setCondition(path, ifSchema) {
|
|
1538
|
+
if (!canAddChild(path.slice(0, -1))) return;
|
|
1539
|
+
commit(setCondition(modelRef.current, path, ifSchema));
|
|
1540
|
+
},
|
|
1541
|
+
addElse(path) {
|
|
1542
|
+
if (!canAddChild(path.slice(0, -1))) return;
|
|
1543
|
+
commit(addElse(modelRef.current, path));
|
|
1544
|
+
},
|
|
1545
|
+
removeElse(path) {
|
|
1546
|
+
if (!canAddChild(path.slice(0, -1))) return;
|
|
1547
|
+
commit(removeElse(modelRef.current, path));
|
|
1548
|
+
const elsePath = [...path, slot("else")];
|
|
1549
|
+
setSelection(
|
|
1550
|
+
(prev) => prev && pathStartsWith(prev, elsePath) ? null : prev
|
|
1551
|
+
);
|
|
1552
|
+
},
|
|
698
1553
|
deleteProperty(path) {
|
|
699
1554
|
if (!canEditProperty(path)) return;
|
|
700
1555
|
commit(deleteProperty(modelRef.current, path));
|
|
@@ -718,9 +1573,48 @@ function useDesignerState(args) {
|
|
|
718
1573
|
if (!canEditProperty(path)) return;
|
|
719
1574
|
commit(updateProperty(modelRef.current, path, patch));
|
|
720
1575
|
},
|
|
721
|
-
changePropertyType(path,
|
|
1576
|
+
changePropertyType(path, next) {
|
|
722
1577
|
if (!canEditProperty(path)) return;
|
|
723
|
-
commit(changePropertyType(modelRef.current, path,
|
|
1578
|
+
commit(changePropertyType(modelRef.current, path, next));
|
|
1579
|
+
},
|
|
1580
|
+
setReference(path, name) {
|
|
1581
|
+
if (!canEditProperty(path)) return;
|
|
1582
|
+
commit(setReference(modelRef.current, path, name));
|
|
1583
|
+
},
|
|
1584
|
+
// Definitions are document-level, not path-scoped: gate on lock only.
|
|
1585
|
+
addDefinition(name) {
|
|
1586
|
+
if (capabilities.locked) return;
|
|
1587
|
+
const res = addDefinition(modelRef.current, name);
|
|
1588
|
+
commit(res.model);
|
|
1589
|
+
setSelection(res.path);
|
|
1590
|
+
},
|
|
1591
|
+
renameDefinition(oldName, newName) {
|
|
1592
|
+
if (capabilities.locked) return;
|
|
1593
|
+
const res = renameDefinition(modelRef.current, oldName, newName);
|
|
1594
|
+
commit(res.model);
|
|
1595
|
+
setSelection(
|
|
1596
|
+
(prev) => prev && samePath(prev, [def(oldName)]) ? res.path : prev
|
|
1597
|
+
);
|
|
1598
|
+
},
|
|
1599
|
+
removeDefinition(name) {
|
|
1600
|
+
if (capabilities.locked) return;
|
|
1601
|
+
commit(removeDefinition(modelRef.current, name));
|
|
1602
|
+
setSelection(
|
|
1603
|
+
(prev) => prev && pathStartsWith(prev, [def(name)]) ? null : prev
|
|
1604
|
+
);
|
|
1605
|
+
},
|
|
1606
|
+
// Replace the whole JSON Schema (from the editable JSON Schema tab).
|
|
1607
|
+
// The uiSchema is preserved. Gated by capabilities.locked only -- a
|
|
1608
|
+
// wholesale replace is not subject to per-path scoping.
|
|
1609
|
+
replaceSchema(schema) {
|
|
1610
|
+
if (capabilities.locked) return;
|
|
1611
|
+
commit({ schema, uiSchema: modelRef.current.uiSchema });
|
|
1612
|
+
setSelection(null);
|
|
1613
|
+
},
|
|
1614
|
+
// Replace the whole UI Schema map (from the editable UI Schema tab).
|
|
1615
|
+
replaceUiSchema(uiSchema) {
|
|
1616
|
+
if (readOnly) return;
|
|
1617
|
+
commit({ schema: modelRef.current.schema, uiSchema });
|
|
724
1618
|
},
|
|
725
1619
|
setEnum(path, choices) {
|
|
726
1620
|
if (!canEditProperty(path)) return;
|
|
@@ -757,18 +1651,67 @@ function useDesignerState(args) {
|
|
|
757
1651
|
if (readOnly) return;
|
|
758
1652
|
commit(setScopedLayoutOptions(modelRef.current, scopePath, options));
|
|
759
1653
|
},
|
|
1654
|
+
setScopedLayoutGroups(scopePath, groups) {
|
|
1655
|
+
if (readOnly) return;
|
|
1656
|
+
commit(setScopedLayoutGroups(modelRef.current, scopePath, groups));
|
|
1657
|
+
},
|
|
1658
|
+
setScopedGroupRows(scopePath, groupIndex, rows) {
|
|
1659
|
+
if (readOnly) return;
|
|
1660
|
+
commit(
|
|
1661
|
+
setScopedGroupRows(modelRef.current, scopePath, groupIndex, rows)
|
|
1662
|
+
);
|
|
1663
|
+
},
|
|
1664
|
+
setScopedLayoutMode(scopePath, mode) {
|
|
1665
|
+
if (readOnly) return;
|
|
1666
|
+
commit(setScopedLayoutMode(modelRef.current, scopePath, mode));
|
|
1667
|
+
},
|
|
1668
|
+
addLayoutGroup(scopePath, label) {
|
|
1669
|
+
if (readOnly) return;
|
|
1670
|
+
commit(addLayoutGroup(modelRef.current, scopePath, label));
|
|
1671
|
+
},
|
|
1672
|
+
renameLayoutGroup(scopePath, groupIndex, label) {
|
|
1673
|
+
if (readOnly) return;
|
|
1674
|
+
commit(
|
|
1675
|
+
renameLayoutGroup(modelRef.current, scopePath, groupIndex, label)
|
|
1676
|
+
);
|
|
1677
|
+
},
|
|
1678
|
+
removeLayoutGroup(scopePath, groupIndex) {
|
|
1679
|
+
if (readOnly) return;
|
|
1680
|
+
commit(removeLayoutGroup(modelRef.current, scopePath, groupIndex));
|
|
1681
|
+
},
|
|
1682
|
+
moveLayoutGroup(scopePath, groupIndex, direction) {
|
|
1683
|
+
if (readOnly) return;
|
|
1684
|
+
commit(
|
|
1685
|
+
moveLayoutGroup(modelRef.current, scopePath, groupIndex, direction)
|
|
1686
|
+
);
|
|
1687
|
+
},
|
|
1688
|
+
setLayoutGroupRule(scopePath, groupIndex, rule) {
|
|
1689
|
+
if (readOnly) return;
|
|
1690
|
+
commit(
|
|
1691
|
+
setLayoutGroupRule(modelRef.current, scopePath, groupIndex, rule)
|
|
1692
|
+
);
|
|
1693
|
+
},
|
|
1694
|
+
setScopedLayoutVariant(scopePath, variant) {
|
|
1695
|
+
if (readOnly) return;
|
|
1696
|
+
commit(setScopedLayoutVariant(modelRef.current, scopePath, variant));
|
|
1697
|
+
},
|
|
1698
|
+
placeFieldInGroup(scopePath, key, target) {
|
|
1699
|
+
if (readOnly) return;
|
|
1700
|
+
commit(placeFieldInGroup(modelRef.current, scopePath, key, target));
|
|
1701
|
+
},
|
|
760
1702
|
setGlobalOptions(options) {
|
|
761
1703
|
if (readOnly) return;
|
|
762
1704
|
commit(setGlobalOptions(modelRef.current, options));
|
|
763
1705
|
}
|
|
764
1706
|
}),
|
|
765
|
-
[commit, readOnly, canEditProperty, canAddChild]
|
|
1707
|
+
[commit, readOnly, capabilities.locked, canEditProperty, canAddChild]
|
|
766
1708
|
);
|
|
767
1709
|
return {
|
|
768
1710
|
schema: model.schema,
|
|
769
1711
|
uiSchema: model.uiSchema,
|
|
770
1712
|
selection,
|
|
771
1713
|
readOnly,
|
|
1714
|
+
refResolver: args.refResolver,
|
|
772
1715
|
capabilities,
|
|
773
1716
|
canEditProperty,
|
|
774
1717
|
canAddChild,
|
|
@@ -778,7 +1721,7 @@ function useDesignerState(args) {
|
|
|
778
1721
|
}
|
|
779
1722
|
|
|
780
1723
|
// src/SchemaDesigner.tsx
|
|
781
|
-
import { useEffect as
|
|
1724
|
+
import { useEffect as useEffect6, useState as useState9 } from "react";
|
|
782
1725
|
import { ResizeHandle, IconButton as IconButton5 } from "@petrarca/sonnet-ui";
|
|
783
1726
|
import { useResizablePanel } from "@petrarca/sonnet-core/hooks";
|
|
784
1727
|
import {
|
|
@@ -951,7 +1894,7 @@ function FormSettingsSheet({
|
|
|
951
1894
|
}
|
|
952
1895
|
|
|
953
1896
|
// src/tree/PropertyTree.tsx
|
|
954
|
-
import { useEffect, useRef as useRef2, useState as useState2 } from "react";
|
|
1897
|
+
import { useEffect, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
|
|
955
1898
|
import { cn } from "@petrarca/sonnet-core";
|
|
956
1899
|
import { IconButton, Input } from "@petrarca/sonnet-ui";
|
|
957
1900
|
import {
|
|
@@ -960,30 +1903,90 @@ import {
|
|
|
960
1903
|
ChevronUp,
|
|
961
1904
|
ChevronDown,
|
|
962
1905
|
PanelLeftClose,
|
|
963
|
-
Lock
|
|
1906
|
+
Lock,
|
|
1907
|
+
GitBranch,
|
|
1908
|
+
SplitSquareHorizontal,
|
|
1909
|
+
Eye,
|
|
1910
|
+
Braces,
|
|
1911
|
+
Brackets,
|
|
1912
|
+
GitFork,
|
|
1913
|
+
CornerDownRight,
|
|
1914
|
+
Type,
|
|
1915
|
+
List,
|
|
1916
|
+
ToggleLeft,
|
|
1917
|
+
Hash,
|
|
1918
|
+
Link2,
|
|
1919
|
+
Puzzle,
|
|
1920
|
+
ChevronRight
|
|
964
1921
|
} from "lucide-react";
|
|
965
1922
|
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1923
|
+
function buildInUseDefs(schema, names) {
|
|
1924
|
+
return new Set(names.filter((n) => definitionUsageCount(schema, n) > 0));
|
|
1925
|
+
}
|
|
966
1926
|
function PropertyTree({
|
|
967
1927
|
state,
|
|
968
1928
|
onCollapse
|
|
969
1929
|
}) {
|
|
970
|
-
const nodes =
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
1930
|
+
const nodes = useMemo2(
|
|
1931
|
+
() => buildPropertyTree(state.schema, state.refResolver),
|
|
1932
|
+
[state.schema, state.refResolver]
|
|
1933
|
+
);
|
|
1934
|
+
const propertyNodes = useMemo2(
|
|
1935
|
+
() => nodes.filter((n) => n.kind !== "definition"),
|
|
1936
|
+
[nodes]
|
|
1937
|
+
);
|
|
1938
|
+
const definitionNodes = useMemo2(
|
|
1939
|
+
() => nodes.filter((n) => n.kind === "definition"),
|
|
1940
|
+
[nodes]
|
|
1941
|
+
);
|
|
1942
|
+
const canEditDefs = !state.capabilities.locked;
|
|
1943
|
+
const [propsOpen, setPropsOpen] = useState2(true);
|
|
1944
|
+
return /* @__PURE__ */ jsxs2("div", { className: "flex h-full flex-col min-h-0", children: [
|
|
1945
|
+
/* @__PURE__ */ jsxs2("div", { className: "mb-2 flex items-center gap-2 shrink-0", children: [
|
|
1946
|
+
/* @__PURE__ */ jsxs2(
|
|
1947
|
+
"button",
|
|
1948
|
+
{
|
|
1949
|
+
type: "button",
|
|
1950
|
+
className: "flex items-center gap-1 heading-meta",
|
|
1951
|
+
onClick: () => setPropsOpen((v) => !v),
|
|
1952
|
+
children: [
|
|
1953
|
+
propsOpen ? /* @__PURE__ */ jsx2(ChevronDown, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx2(ChevronRight, { className: "h-3 w-3" }),
|
|
1954
|
+
"Properties",
|
|
1955
|
+
propertyNodes.length > 0 && /* @__PURE__ */ jsxs2("span", { className: "text-muted-foreground/60", children: [
|
|
1956
|
+
"(",
|
|
1957
|
+
propertyNodes.length,
|
|
1958
|
+
")"
|
|
1959
|
+
] })
|
|
1960
|
+
]
|
|
1961
|
+
}
|
|
1962
|
+
),
|
|
974
1963
|
/* @__PURE__ */ jsxs2("span", { className: "ml-auto flex gap-1", children: [
|
|
975
1964
|
state.canAddChild([]) && /* @__PURE__ */ jsx2(
|
|
976
1965
|
IconButton,
|
|
977
1966
|
{
|
|
978
1967
|
label: "Add property",
|
|
979
|
-
onClick: () =>
|
|
1968
|
+
onClick: () => {
|
|
1969
|
+
setPropsOpen(true);
|
|
1970
|
+
state.addProperty([], "scalar");
|
|
1971
|
+
},
|
|
980
1972
|
children: /* @__PURE__ */ jsx2(Plus, {})
|
|
981
1973
|
}
|
|
982
1974
|
),
|
|
1975
|
+
state.canAddChild([]) && /* @__PURE__ */ jsx2(
|
|
1976
|
+
IconButton,
|
|
1977
|
+
{
|
|
1978
|
+
label: "Add condition",
|
|
1979
|
+
onClick: () => {
|
|
1980
|
+
setPropsOpen(true);
|
|
1981
|
+
state.addConditional([]);
|
|
1982
|
+
},
|
|
1983
|
+
children: /* @__PURE__ */ jsx2(GitBranch, {})
|
|
1984
|
+
}
|
|
1985
|
+
),
|
|
983
1986
|
onCollapse && /* @__PURE__ */ jsx2(IconButton, { label: "Collapse properties", onClick: onCollapse, children: /* @__PURE__ */ jsx2(PanelLeftClose, {}) })
|
|
984
1987
|
] })
|
|
985
1988
|
] }),
|
|
986
|
-
|
|
1989
|
+
propsOpen && (propertyNodes.length === 0 ? /* @__PURE__ */ jsx2("p", { className: "text-xs text-muted-foreground", children: "No properties yet." }) : /* @__PURE__ */ jsx2("div", { className: "flex-1 min-h-0 overflow-auto rounded-lg border divide-y", children: propertyNodes.map((node) => /* @__PURE__ */ jsx2(
|
|
987
1990
|
PropertyRow,
|
|
988
1991
|
{
|
|
989
1992
|
node,
|
|
@@ -991,7 +1994,72 @@ function PropertyTree({
|
|
|
991
1994
|
depth: 0
|
|
992
1995
|
},
|
|
993
1996
|
pathKey(node.path)
|
|
994
|
-
)) })
|
|
1997
|
+
)) })),
|
|
1998
|
+
/* @__PURE__ */ jsx2(
|
|
1999
|
+
DefinitionsRegion,
|
|
2000
|
+
{
|
|
2001
|
+
nodes: definitionNodes,
|
|
2002
|
+
state,
|
|
2003
|
+
canEdit: canEditDefs
|
|
2004
|
+
}
|
|
2005
|
+
)
|
|
2006
|
+
] });
|
|
2007
|
+
}
|
|
2008
|
+
function DefinitionsRegion({
|
|
2009
|
+
nodes,
|
|
2010
|
+
state,
|
|
2011
|
+
canEdit
|
|
2012
|
+
}) {
|
|
2013
|
+
const [open, setOpen] = useState2(false);
|
|
2014
|
+
const inUseDefs = useMemo2(
|
|
2015
|
+
() => buildInUseDefs(
|
|
2016
|
+
state.schema,
|
|
2017
|
+
nodes.map((n) => n.key)
|
|
2018
|
+
),
|
|
2019
|
+
[state.schema, nodes]
|
|
2020
|
+
);
|
|
2021
|
+
if (nodes.length === 0 && !canEdit) return null;
|
|
2022
|
+
return /* @__PURE__ */ jsxs2("div", { className: "mt-3 shrink-0", children: [
|
|
2023
|
+
/* @__PURE__ */ jsxs2("div", { className: "mb-2 flex items-center gap-2", children: [
|
|
2024
|
+
/* @__PURE__ */ jsxs2(
|
|
2025
|
+
"button",
|
|
2026
|
+
{
|
|
2027
|
+
type: "button",
|
|
2028
|
+
className: "flex items-center gap-1 heading-meta",
|
|
2029
|
+
onClick: () => setOpen((v) => !v),
|
|
2030
|
+
children: [
|
|
2031
|
+
open ? /* @__PURE__ */ jsx2(ChevronDown, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx2(ChevronRight, { className: "h-3 w-3" }),
|
|
2032
|
+
"Definitions",
|
|
2033
|
+
nodes.length > 0 && /* @__PURE__ */ jsxs2("span", { className: "text-muted-foreground/60", children: [
|
|
2034
|
+
"(",
|
|
2035
|
+
nodes.length,
|
|
2036
|
+
")"
|
|
2037
|
+
] })
|
|
2038
|
+
]
|
|
2039
|
+
}
|
|
2040
|
+
),
|
|
2041
|
+
canEdit && /* @__PURE__ */ jsx2("span", { className: "ml-auto flex gap-1", children: /* @__PURE__ */ jsx2(
|
|
2042
|
+
IconButton,
|
|
2043
|
+
{
|
|
2044
|
+
label: "Add definition",
|
|
2045
|
+
onClick: () => {
|
|
2046
|
+
setOpen(true);
|
|
2047
|
+
state.addDefinition();
|
|
2048
|
+
},
|
|
2049
|
+
children: /* @__PURE__ */ jsx2(Plus, {})
|
|
2050
|
+
}
|
|
2051
|
+
) })
|
|
2052
|
+
] }),
|
|
2053
|
+
open && (nodes.length === 0 ? /* @__PURE__ */ jsx2("p", { className: "text-xs text-muted-foreground", children: "No definitions yet." }) : /* @__PURE__ */ jsx2("div", { className: "max-h-64 overflow-auto rounded-lg border divide-y", children: nodes.map((node) => /* @__PURE__ */ jsx2(
|
|
2054
|
+
PropertyRow,
|
|
2055
|
+
{
|
|
2056
|
+
node,
|
|
2057
|
+
state,
|
|
2058
|
+
depth: 0,
|
|
2059
|
+
inUseDefs
|
|
2060
|
+
},
|
|
2061
|
+
pathKey(node.path)
|
|
2062
|
+
)) }))
|
|
995
2063
|
] });
|
|
996
2064
|
}
|
|
997
2065
|
function NameEditor({
|
|
@@ -1031,23 +2099,82 @@ function NameEditor({
|
|
|
1031
2099
|
}
|
|
1032
2100
|
);
|
|
1033
2101
|
}
|
|
2102
|
+
var KIND_ICONS = {
|
|
2103
|
+
reference: { Icon: Link2, label: "reference" },
|
|
2104
|
+
definition: { Icon: Puzzle, label: "definition" },
|
|
2105
|
+
conditional: { Icon: GitBranch, label: "condition" },
|
|
2106
|
+
object: { Icon: Braces, label: "object" },
|
|
2107
|
+
array: { Icon: Brackets, label: "array" },
|
|
2108
|
+
oneOf: { Icon: GitFork, label: "one of" },
|
|
2109
|
+
enum: { Icon: List, label: "choice" }
|
|
2110
|
+
};
|
|
2111
|
+
function kindIconFor(node) {
|
|
2112
|
+
const last = node.path[node.path.length - 1];
|
|
2113
|
+
if (last?.kind === "slot") return { Icon: CornerDownRight, label: "branch" };
|
|
2114
|
+
if (node.slotKind === "branch") return { Icon: GitFork, label: "variant" };
|
|
2115
|
+
return KIND_ICONS[node.kind] ?? scalarIcon(node.property.type);
|
|
2116
|
+
}
|
|
2117
|
+
function scalarIcon(type) {
|
|
2118
|
+
if (type === "number" || type === "integer")
|
|
2119
|
+
return { Icon: Hash, label: "number" };
|
|
2120
|
+
if (type === "boolean") return { Icon: ToggleLeft, label: "boolean" };
|
|
2121
|
+
return { Icon: Type, label: "text" };
|
|
2122
|
+
}
|
|
2123
|
+
function KindIcon({ node }) {
|
|
2124
|
+
const { Icon, label } = kindIconFor(node);
|
|
2125
|
+
return /* @__PURE__ */ jsx2(
|
|
2126
|
+
Icon,
|
|
2127
|
+
{
|
|
2128
|
+
className: "h-3.5 w-3.5 shrink-0 text-muted-foreground/60",
|
|
2129
|
+
"aria-label": label
|
|
2130
|
+
}
|
|
2131
|
+
);
|
|
2132
|
+
}
|
|
2133
|
+
function Markers({ node }) {
|
|
2134
|
+
const rule = node.property["x-ui-rule"];
|
|
2135
|
+
if (!node.required && !rule) return null;
|
|
2136
|
+
const scopeField = rule?.condition.scope.replace(/^\//, "");
|
|
2137
|
+
const ruleTip = rule ? `${rule.effect} when ${scopeField || rule.condition.scope}` : "";
|
|
2138
|
+
return /* @__PURE__ */ jsxs2("span", { className: "shrink-0 flex items-center gap-1 group-hover/row:hidden", children: [
|
|
2139
|
+
node.required && /* @__PURE__ */ jsx2(
|
|
2140
|
+
"span",
|
|
2141
|
+
{
|
|
2142
|
+
className: "text-destructive text-xs leading-none",
|
|
2143
|
+
title: "required",
|
|
2144
|
+
"aria-label": "required",
|
|
2145
|
+
children: "*"
|
|
2146
|
+
}
|
|
2147
|
+
),
|
|
2148
|
+
rule && /* @__PURE__ */ jsx2("span", { title: ruleTip, "aria-label": ruleTip, className: "flex", children: /* @__PURE__ */ jsx2(Eye, { className: "h-3 w-3 text-primary" }) })
|
|
2149
|
+
] });
|
|
2150
|
+
}
|
|
2151
|
+
function canAddChildHere(node, state, isConditional) {
|
|
2152
|
+
if (!acceptsChildren(node)) return false;
|
|
2153
|
+
return node.kind === "oneOf" || isConditional ? state.canEditProperty(node.path) : state.canAddChild(node.path);
|
|
2154
|
+
}
|
|
2155
|
+
function isRuleStyled(node) {
|
|
2156
|
+
const last = node.path[node.path.length - 1];
|
|
2157
|
+
return node.kind === "conditional" || last?.kind === "slot";
|
|
2158
|
+
}
|
|
1034
2159
|
function RowLabel({
|
|
1035
2160
|
node,
|
|
1036
2161
|
editable,
|
|
1037
2162
|
onStartRename
|
|
1038
2163
|
}) {
|
|
1039
2164
|
const title = editable ? `${node.key}${node.required ? " (required)" : ""} -- double-click to rename` : `${node.key} -- fixed (not editable)`;
|
|
2165
|
+
const ruleStyled = isRuleStyled(node);
|
|
1040
2166
|
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
1041
|
-
/* @__PURE__ */
|
|
2167
|
+
/* @__PURE__ */ jsx2(KindIcon, { node }),
|
|
2168
|
+
/* @__PURE__ */ jsx2(
|
|
1042
2169
|
"span",
|
|
1043
2170
|
{
|
|
1044
|
-
className:
|
|
2171
|
+
className: cn(
|
|
2172
|
+
"flex-1 min-w-0 truncate text-sm",
|
|
2173
|
+
ruleStyled ? "font-mono text-xs uppercase tracking-wide text-muted-foreground" : "font-medium"
|
|
2174
|
+
),
|
|
1045
2175
|
onDoubleClick: onStartRename,
|
|
1046
2176
|
title,
|
|
1047
|
-
children:
|
|
1048
|
-
node.key,
|
|
1049
|
-
node.required && /* @__PURE__ */ jsx2("span", { className: "ml-0.5 text-destructive", "aria-label": "required", children: "*" })
|
|
1050
|
-
]
|
|
2177
|
+
children: node.key
|
|
1051
2178
|
}
|
|
1052
2179
|
),
|
|
1053
2180
|
!editable && /* @__PURE__ */ jsx2(
|
|
@@ -1057,89 +2184,192 @@ function RowLabel({
|
|
|
1057
2184
|
"aria-label": "fixed"
|
|
1058
2185
|
}
|
|
1059
2186
|
),
|
|
2187
|
+
/* @__PURE__ */ jsx2(Markers, { node }),
|
|
1060
2188
|
/* @__PURE__ */ jsx2(
|
|
1061
2189
|
"span",
|
|
1062
2190
|
{
|
|
1063
|
-
className: "shrink-0 max-w-[
|
|
2191
|
+
className: "shrink-0 max-w-[40%] truncate font-mono text-[11px] text-muted-foreground/70 group-hover/row:hidden",
|
|
1064
2192
|
title: node.typeLabel,
|
|
1065
2193
|
children: node.typeLabel
|
|
1066
2194
|
}
|
|
1067
2195
|
)
|
|
1068
2196
|
] });
|
|
1069
2197
|
}
|
|
1070
|
-
function
|
|
2198
|
+
function isConditionalMember2(node) {
|
|
2199
|
+
return node.kind === "conditional";
|
|
2200
|
+
}
|
|
2201
|
+
function isConditionalSlot(node) {
|
|
2202
|
+
const last = node.path[node.path.length - 1];
|
|
2203
|
+
return node.slotKind === "conditional" && last?.kind === "slot";
|
|
2204
|
+
}
|
|
2205
|
+
function acceptsChildren(node) {
|
|
2206
|
+
return node.kind === "object" || node.kind === "array" || node.kind === "oneOf" || node.kind === "definition" || node.slotKind === "branch" || isConditionalSlot(node);
|
|
2207
|
+
}
|
|
2208
|
+
function acceptsConditions(node) {
|
|
2209
|
+
return node.kind === "object" || isConditionalSlot(node);
|
|
2210
|
+
}
|
|
2211
|
+
function ActionButton({
|
|
2212
|
+
label,
|
|
2213
|
+
tone,
|
|
2214
|
+
disabled,
|
|
2215
|
+
onAct,
|
|
2216
|
+
children
|
|
2217
|
+
}) {
|
|
2218
|
+
return /* @__PURE__ */ jsx2(
|
|
2219
|
+
IconButton,
|
|
2220
|
+
{
|
|
2221
|
+
label,
|
|
2222
|
+
tone,
|
|
2223
|
+
disabled,
|
|
2224
|
+
revealOnHover: true,
|
|
2225
|
+
onClick: (e) => {
|
|
2226
|
+
e.stopPropagation();
|
|
2227
|
+
onAct();
|
|
2228
|
+
},
|
|
2229
|
+
children
|
|
2230
|
+
}
|
|
2231
|
+
);
|
|
2232
|
+
}
|
|
2233
|
+
function MoveActions({
|
|
2234
|
+
node,
|
|
2235
|
+
state
|
|
2236
|
+
}) {
|
|
2237
|
+
const isBranch = node.slotKind === "branch";
|
|
2238
|
+
const move = (dir) => isBranch ? state.moveBranch(node.path, dir) : state.moveProperty(node.path, dir);
|
|
2239
|
+
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
2240
|
+
/* @__PURE__ */ jsx2(ActionButton, { label: "Move up", onAct: () => move(-1), children: /* @__PURE__ */ jsx2(ChevronUp, {}) }),
|
|
2241
|
+
/* @__PURE__ */ jsx2(ActionButton, { label: "Move down", onAct: () => move(1), children: /* @__PURE__ */ jsx2(ChevronDown, {}) })
|
|
2242
|
+
] });
|
|
2243
|
+
}
|
|
2244
|
+
function AddActions({
|
|
1071
2245
|
node,
|
|
1072
2246
|
state,
|
|
1073
2247
|
editable,
|
|
1074
2248
|
canAddHere
|
|
1075
2249
|
}) {
|
|
2250
|
+
const isOneOf3 = node.kind === "oneOf";
|
|
2251
|
+
const addChild = () => isOneOf3 ? state.addBranch(node.path) : state.addProperty(node.path, "scalar");
|
|
2252
|
+
const condMember = isConditionalMember2(node) && editable;
|
|
2253
|
+
const hasElse = !!node.property.else;
|
|
1076
2254
|
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
1077
|
-
editable && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
1078
|
-
/* @__PURE__ */ jsx2(
|
|
1079
|
-
IconButton,
|
|
1080
|
-
{
|
|
1081
|
-
label: "Move up",
|
|
1082
|
-
revealOnHover: true,
|
|
1083
|
-
onClick: (e) => {
|
|
1084
|
-
e.stopPropagation();
|
|
1085
|
-
state.moveProperty(node.path, -1);
|
|
1086
|
-
},
|
|
1087
|
-
children: /* @__PURE__ */ jsx2(ChevronUp, {})
|
|
1088
|
-
}
|
|
1089
|
-
),
|
|
1090
|
-
/* @__PURE__ */ jsx2(
|
|
1091
|
-
IconButton,
|
|
1092
|
-
{
|
|
1093
|
-
label: "Move down",
|
|
1094
|
-
revealOnHover: true,
|
|
1095
|
-
onClick: (e) => {
|
|
1096
|
-
e.stopPropagation();
|
|
1097
|
-
state.moveProperty(node.path, 1);
|
|
1098
|
-
},
|
|
1099
|
-
children: /* @__PURE__ */ jsx2(ChevronDown, {})
|
|
1100
|
-
}
|
|
1101
|
-
)
|
|
1102
|
-
] }),
|
|
1103
2255
|
canAddHere && /* @__PURE__ */ jsx2(
|
|
1104
|
-
|
|
2256
|
+
ActionButton,
|
|
1105
2257
|
{
|
|
1106
|
-
label: "Add child property",
|
|
1107
|
-
|
|
1108
|
-
onClick: (e) => {
|
|
1109
|
-
e.stopPropagation();
|
|
1110
|
-
state.addProperty(node.path, "scalar");
|
|
1111
|
-
},
|
|
2258
|
+
label: isOneOf3 ? "Add branch" : "Add child property",
|
|
2259
|
+
onAct: addChild,
|
|
1112
2260
|
children: /* @__PURE__ */ jsx2(Plus, {})
|
|
1113
2261
|
}
|
|
1114
2262
|
),
|
|
1115
|
-
editable && /* @__PURE__ */ jsx2(
|
|
1116
|
-
|
|
2263
|
+
editable && acceptsConditions(node) && /* @__PURE__ */ jsx2(
|
|
2264
|
+
ActionButton,
|
|
1117
2265
|
{
|
|
1118
|
-
label: "
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
onClick: (e) => {
|
|
1122
|
-
e.stopPropagation();
|
|
1123
|
-
state.deleteProperty(node.path);
|
|
1124
|
-
},
|
|
1125
|
-
children: /* @__PURE__ */ jsx2(Trash2, {})
|
|
2266
|
+
label: "Add condition",
|
|
2267
|
+
onAct: () => state.addConditional(node.path),
|
|
2268
|
+
children: /* @__PURE__ */ jsx2(GitBranch, {})
|
|
1126
2269
|
}
|
|
1127
|
-
)
|
|
2270
|
+
),
|
|
2271
|
+
condMember && !hasElse && /* @__PURE__ */ jsx2(ActionButton, { label: "Add else", onAct: () => state.addElse(node.path), children: /* @__PURE__ */ jsx2(SplitSquareHorizontal, {}) })
|
|
2272
|
+
] });
|
|
2273
|
+
}
|
|
2274
|
+
function lastSegment(node) {
|
|
2275
|
+
return node.path[node.path.length - 1];
|
|
2276
|
+
}
|
|
2277
|
+
function removeDescriptor(node, state, inUseDefs) {
|
|
2278
|
+
const last = lastSegment(node);
|
|
2279
|
+
const slotName = last?.kind === "slot" ? last.slot : void 0;
|
|
2280
|
+
if (slotName === "then") return null;
|
|
2281
|
+
if (node.slotKind === "branch") {
|
|
2282
|
+
return simpleRemove("Remove branch", () => state.removeBranch(node.path));
|
|
2283
|
+
}
|
|
2284
|
+
if (isConditionalMember2(node)) {
|
|
2285
|
+
return simpleRemove(
|
|
2286
|
+
"Remove condition",
|
|
2287
|
+
() => state.removeConditional(node.path)
|
|
2288
|
+
);
|
|
2289
|
+
}
|
|
2290
|
+
if (node.kind === "definition") {
|
|
2291
|
+
const inUse = inUseDefs.has(node.key);
|
|
2292
|
+
return {
|
|
2293
|
+
label: inUse ? "In use -- remove references first" : "Delete definition",
|
|
2294
|
+
disabled: inUse,
|
|
2295
|
+
remove: () => state.removeDefinition(node.key)
|
|
2296
|
+
};
|
|
2297
|
+
}
|
|
2298
|
+
if (slotName === "else") {
|
|
2299
|
+
return simpleRemove(
|
|
2300
|
+
"Remove else",
|
|
2301
|
+
() => state.removeElse(node.path.slice(0, -1))
|
|
2302
|
+
);
|
|
2303
|
+
}
|
|
2304
|
+
return simpleRemove("Delete property", () => state.deleteProperty(node.path));
|
|
2305
|
+
}
|
|
2306
|
+
function simpleRemove(label, remove) {
|
|
2307
|
+
return { label, disabled: false, remove };
|
|
2308
|
+
}
|
|
2309
|
+
function RemoveAction({
|
|
2310
|
+
node,
|
|
2311
|
+
state,
|
|
2312
|
+
inUseDefs
|
|
2313
|
+
}) {
|
|
2314
|
+
const desc = removeDescriptor(node, state, inUseDefs);
|
|
2315
|
+
if (!desc) return null;
|
|
2316
|
+
return /* @__PURE__ */ jsx2(
|
|
2317
|
+
ActionButton,
|
|
2318
|
+
{
|
|
2319
|
+
label: desc.label,
|
|
2320
|
+
tone: "destructive",
|
|
2321
|
+
disabled: desc.disabled,
|
|
2322
|
+
onAct: desc.remove,
|
|
2323
|
+
children: /* @__PURE__ */ jsx2(Trash2, {})
|
|
2324
|
+
}
|
|
2325
|
+
);
|
|
2326
|
+
}
|
|
2327
|
+
var EMPTY_SET = /* @__PURE__ */ new Set();
|
|
2328
|
+
function RowActions({
|
|
2329
|
+
node,
|
|
2330
|
+
state,
|
|
2331
|
+
editable,
|
|
2332
|
+
canAddHere,
|
|
2333
|
+
inUseDefs = EMPTY_SET
|
|
2334
|
+
}) {
|
|
2335
|
+
const reorderable = editable && node.slotKind !== "conditional" && node.slotKind !== "definition";
|
|
2336
|
+
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
2337
|
+
reorderable && /* @__PURE__ */ jsx2(MoveActions, { node, state }),
|
|
2338
|
+
/* @__PURE__ */ jsx2(
|
|
2339
|
+
AddActions,
|
|
2340
|
+
{
|
|
2341
|
+
node,
|
|
2342
|
+
state,
|
|
2343
|
+
editable,
|
|
2344
|
+
canAddHere
|
|
2345
|
+
}
|
|
2346
|
+
),
|
|
2347
|
+
editable && /* @__PURE__ */ jsx2(RemoveAction, { node, state, inUseDefs })
|
|
1128
2348
|
] });
|
|
1129
2349
|
}
|
|
1130
2350
|
function PropertyRow({
|
|
1131
2351
|
node,
|
|
1132
2352
|
state,
|
|
1133
|
-
depth
|
|
2353
|
+
depth,
|
|
2354
|
+
inUseDefs = EMPTY_SET
|
|
1134
2355
|
}) {
|
|
1135
2356
|
const selected = samePath(state.selection, node.path);
|
|
1136
2357
|
const [editing, setEditing] = useState2(false);
|
|
1137
|
-
const editable = state.canEditProperty(node.path);
|
|
1138
|
-
const
|
|
1139
|
-
const
|
|
2358
|
+
const editable = !node.readOnly && state.canEditProperty(node.path);
|
|
2359
|
+
const isBranch = node.slotKind === "branch";
|
|
2360
|
+
const isConditional = node.slotKind === "conditional";
|
|
2361
|
+
const isDefinition = node.kind === "definition";
|
|
2362
|
+
const editableName = isDefinition ? !state.capabilities.locked : editable;
|
|
2363
|
+
const canAddHere = canAddChildHere(node, state, isConditional);
|
|
2364
|
+
const showActions = editableName || canAddHere;
|
|
1140
2365
|
const startRename = (e) => {
|
|
1141
2366
|
e.stopPropagation();
|
|
1142
|
-
if (
|
|
2367
|
+
if (editableName && !isBranch && !isConditional) setEditing(true);
|
|
2368
|
+
};
|
|
2369
|
+
const commitRename = (next) => {
|
|
2370
|
+
setEditing(false);
|
|
2371
|
+
if (isDefinition) state.renameDefinition(node.key, next);
|
|
2372
|
+
else state.renameProperty(node.path, next);
|
|
1143
2373
|
};
|
|
1144
2374
|
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
1145
2375
|
/* @__PURE__ */ jsxs2(
|
|
@@ -1156,17 +2386,14 @@ function PropertyRow({
|
|
|
1156
2386
|
NameEditor,
|
|
1157
2387
|
{
|
|
1158
2388
|
initial: node.key,
|
|
1159
|
-
onCommit:
|
|
1160
|
-
setEditing(false);
|
|
1161
|
-
state.renameProperty(node.path, next);
|
|
1162
|
-
},
|
|
2389
|
+
onCommit: commitRename,
|
|
1163
2390
|
onCancel: () => setEditing(false)
|
|
1164
2391
|
}
|
|
1165
2392
|
) : /* @__PURE__ */ jsx2(
|
|
1166
2393
|
RowLabel,
|
|
1167
2394
|
{
|
|
1168
2395
|
node,
|
|
1169
|
-
editable,
|
|
2396
|
+
editable: editableName,
|
|
1170
2397
|
onStartRename: startRename
|
|
1171
2398
|
}
|
|
1172
2399
|
),
|
|
@@ -1187,8 +2414,9 @@ function PropertyRow({
|
|
|
1187
2414
|
{
|
|
1188
2415
|
node,
|
|
1189
2416
|
state,
|
|
1190
|
-
editable,
|
|
1191
|
-
canAddHere
|
|
2417
|
+
editable: editableName,
|
|
2418
|
+
canAddHere,
|
|
2419
|
+
inUseDefs
|
|
1192
2420
|
}
|
|
1193
2421
|
)
|
|
1194
2422
|
}
|
|
@@ -1201,7 +2429,8 @@ function PropertyRow({
|
|
|
1201
2429
|
{
|
|
1202
2430
|
node: child,
|
|
1203
2431
|
state,
|
|
1204
|
-
depth: depth + 1
|
|
2432
|
+
depth: depth + 1,
|
|
2433
|
+
inUseDefs
|
|
1205
2434
|
},
|
|
1206
2435
|
pathKey(child.path)
|
|
1207
2436
|
))
|
|
@@ -1209,29 +2438,27 @@ function PropertyRow({
|
|
|
1209
2438
|
}
|
|
1210
2439
|
|
|
1211
2440
|
// src/preview/PreviewPane.tsx
|
|
1212
|
-
import { useMemo as
|
|
1213
|
-
import { Check, Copy } from "lucide-react";
|
|
2441
|
+
import { useMemo as useMemo7, useState as useState6 } from "react";
|
|
1214
2442
|
import {
|
|
1215
|
-
Button as Button3,
|
|
1216
2443
|
Tabs,
|
|
1217
2444
|
TabsList,
|
|
1218
2445
|
TabsTrigger,
|
|
1219
2446
|
TabsContent,
|
|
1220
2447
|
TabsSeparator
|
|
1221
2448
|
} from "@petrarca/sonnet-ui";
|
|
1222
|
-
import { JsonEditor as JsonEditor2 } from "@petrarca/sonnet-ui/json-editor";
|
|
1223
2449
|
import { JsonSchemaFormRenderer as JsonSchemaFormRenderer3 } from "@petrarca/sonnet-forms";
|
|
1224
|
-
import {
|
|
1225
|
-
|
|
2450
|
+
import {
|
|
2451
|
+
extractSchemaDefaults
|
|
2452
|
+
} from "@petrarca/sonnet-core/schema";
|
|
1226
2453
|
|
|
1227
2454
|
// src/preview/TablePreview.tsx
|
|
1228
|
-
import { useMemo as
|
|
2455
|
+
import { useMemo as useMemo3 } from "react";
|
|
1229
2456
|
import { EntityTable } from "@petrarca/sonnet-ui";
|
|
1230
2457
|
import { toPageResult } from "@petrarca/sonnet-core";
|
|
1231
2458
|
|
|
1232
2459
|
// src/preview/sampleRows.ts
|
|
1233
|
-
function sampleString(
|
|
1234
|
-
switch (
|
|
2460
|
+
function sampleString(prop2, key, i) {
|
|
2461
|
+
switch (prop2.format) {
|
|
1235
2462
|
case "email":
|
|
1236
2463
|
return `user${i + 1}@example.com`;
|
|
1237
2464
|
case "date":
|
|
@@ -1251,13 +2478,13 @@ function titleCase(key) {
|
|
|
1251
2478
|
const s = key.replace(/[_-]+/g, " ").trim();
|
|
1252
2479
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
1253
2480
|
}
|
|
1254
|
-
function sampleValue(
|
|
1255
|
-
if (Array.isArray(
|
|
1256
|
-
return
|
|
2481
|
+
function sampleValue(prop2, key, i) {
|
|
2482
|
+
if (Array.isArray(prop2.enum) && prop2.enum.length > 0) {
|
|
2483
|
+
return prop2.enum[i % prop2.enum.length];
|
|
1257
2484
|
}
|
|
1258
|
-
switch (
|
|
2485
|
+
switch (prop2.type) {
|
|
1259
2486
|
case "string":
|
|
1260
|
-
return sampleString(
|
|
2487
|
+
return sampleString(prop2, key, i);
|
|
1261
2488
|
case "number":
|
|
1262
2489
|
return Math.round((i + 1) * 1.5 * 100) / 100;
|
|
1263
2490
|
case "integer":
|
|
@@ -1276,8 +2503,8 @@ function sampleRows(schema, count = 3) {
|
|
|
1276
2503
|
const entries = Object.entries(schema.properties);
|
|
1277
2504
|
return Array.from({ length: count }, (_, i) => {
|
|
1278
2505
|
const row = {};
|
|
1279
|
-
for (const [key,
|
|
1280
|
-
row[key] = sampleValue(
|
|
2506
|
+
for (const [key, prop2] of entries) {
|
|
2507
|
+
row[key] = sampleValue(prop2, key, i);
|
|
1281
2508
|
}
|
|
1282
2509
|
return row;
|
|
1283
2510
|
});
|
|
@@ -1289,7 +2516,7 @@ function TablePreview({
|
|
|
1289
2516
|
schema,
|
|
1290
2517
|
uiSchema
|
|
1291
2518
|
}) {
|
|
1292
|
-
const data =
|
|
2519
|
+
const data = useMemo3(() => toPageResult(sampleRows(schema)), [schema]);
|
|
1293
2520
|
return /* @__PURE__ */ jsx3(
|
|
1294
2521
|
EntityTable,
|
|
1295
2522
|
{
|
|
@@ -1304,17 +2531,282 @@ function TablePreview({
|
|
|
1304
2531
|
}
|
|
1305
2532
|
|
|
1306
2533
|
// src/designers/FormLayoutDesigner.tsx
|
|
1307
|
-
import { useMemo as
|
|
1308
|
-
import {
|
|
1309
|
-
import {
|
|
2534
|
+
import { useMemo as useMemo5, useState as useState4 } from "react";
|
|
2535
|
+
import { cn as cn2 } from "@petrarca/sonnet-core";
|
|
2536
|
+
import { Input as Input3, IconButton as IconButton2, Button } from "@petrarca/sonnet-ui";
|
|
2537
|
+
import { FormSelect as FormSelect3, JsonSchemaFormRenderer as JsonSchemaFormRenderer2 } from "@petrarca/sonnet-forms";
|
|
1310
2538
|
import {
|
|
1311
2539
|
Plus as Plus2,
|
|
1312
2540
|
Trash2 as Trash22,
|
|
1313
2541
|
ChevronUp as ChevronUp2,
|
|
1314
2542
|
ChevronDown as ChevronDown2,
|
|
2543
|
+
ChevronLeft,
|
|
2544
|
+
ChevronRight as ChevronRight2,
|
|
1315
2545
|
GripVertical
|
|
1316
2546
|
} from "lucide-react";
|
|
2547
|
+
|
|
2548
|
+
// src/inspector/RuleFields.tsx
|
|
2549
|
+
import { useEffect as useEffect2, useMemo as useMemo4, useState as useState3 } from "react";
|
|
2550
|
+
import { FormSelect as FormSelect2 } from "@petrarca/sonnet-forms";
|
|
2551
|
+
|
|
2552
|
+
// src/model/conditions.ts
|
|
2553
|
+
var CONDITION_OPERATORS = [
|
|
2554
|
+
"=",
|
|
2555
|
+
"!=",
|
|
2556
|
+
">",
|
|
2557
|
+
">=",
|
|
2558
|
+
"<",
|
|
2559
|
+
"<=",
|
|
2560
|
+
"exists"
|
|
2561
|
+
];
|
|
2562
|
+
var CONDITION_OPERATOR_LABELS = {
|
|
2563
|
+
"=": "equals",
|
|
2564
|
+
"!=": "not equals",
|
|
2565
|
+
">": "greater than",
|
|
2566
|
+
">=": "greater or equal",
|
|
2567
|
+
"<": "less than",
|
|
2568
|
+
"<=": "less or equal",
|
|
2569
|
+
exists: "exists"
|
|
2570
|
+
};
|
|
2571
|
+
var OPERATOR_OPTIONS = CONDITION_OPERATORS.map((op) => ({
|
|
2572
|
+
value: op,
|
|
2573
|
+
label: CONDITION_OPERATOR_LABELS[op]
|
|
2574
|
+
}));
|
|
2575
|
+
var NUMERIC_OPS = /* @__PURE__ */ new Set([
|
|
2576
|
+
">",
|
|
2577
|
+
">=",
|
|
2578
|
+
"<",
|
|
2579
|
+
"<="
|
|
2580
|
+
]);
|
|
2581
|
+
function coerceValue(value) {
|
|
2582
|
+
if (value.trim() === "") return value;
|
|
2583
|
+
const n = Number(value);
|
|
2584
|
+
return Number.isFinite(n) ? n : value;
|
|
2585
|
+
}
|
|
2586
|
+
function coerceForScope(value, operator, type) {
|
|
2587
|
+
if (NUMERIC_OPS.has(operator)) return coerceValue(value);
|
|
2588
|
+
if (type === "boolean") return value === "true";
|
|
2589
|
+
if (type === "number" || type === "integer") return coerceValue(value);
|
|
2590
|
+
return value;
|
|
2591
|
+
}
|
|
2592
|
+
function operatorToConstraint(operator, value, type) {
|
|
2593
|
+
const v = coerceForScope(value, operator, type);
|
|
2594
|
+
switch (operator) {
|
|
2595
|
+
case "=":
|
|
2596
|
+
return { const: v };
|
|
2597
|
+
case "!=":
|
|
2598
|
+
return { not: { const: v } };
|
|
2599
|
+
case ">":
|
|
2600
|
+
return { exclusiveMinimum: v };
|
|
2601
|
+
case ">=":
|
|
2602
|
+
return { minimum: v };
|
|
2603
|
+
case "<":
|
|
2604
|
+
return { exclusiveMaximum: v };
|
|
2605
|
+
case "<=":
|
|
2606
|
+
return { maximum: v };
|
|
2607
|
+
case "exists":
|
|
2608
|
+
return {};
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2611
|
+
function conditionToIf(condition2, type) {
|
|
2612
|
+
const { field, operator } = condition2;
|
|
2613
|
+
if (!field) return void 0;
|
|
2614
|
+
if (operator === "exists") {
|
|
2615
|
+
return { required: [field] };
|
|
2616
|
+
}
|
|
2617
|
+
return {
|
|
2618
|
+
properties: {
|
|
2619
|
+
[field]: operatorToConstraint(operator, condition2.value, type)
|
|
2620
|
+
},
|
|
2621
|
+
required: [field]
|
|
2622
|
+
};
|
|
2623
|
+
}
|
|
2624
|
+
function constraintToOperator(c) {
|
|
2625
|
+
if (c.not && "const" in c.not)
|
|
2626
|
+
return { operator: "!=", value: String(c.not.const) };
|
|
2627
|
+
if (c.exclusiveMinimum !== void 0)
|
|
2628
|
+
return { operator: ">", value: String(c.exclusiveMinimum) };
|
|
2629
|
+
if (c.minimum !== void 0)
|
|
2630
|
+
return { operator: ">=", value: String(c.minimum) };
|
|
2631
|
+
if (c.exclusiveMaximum !== void 0)
|
|
2632
|
+
return { operator: "<", value: String(c.exclusiveMaximum) };
|
|
2633
|
+
if (c.maximum !== void 0)
|
|
2634
|
+
return { operator: "<=", value: String(c.maximum) };
|
|
2635
|
+
if ("const" in c) return { operator: "=", value: String(c.const) };
|
|
2636
|
+
return { operator: "exists", value: "" };
|
|
2637
|
+
}
|
|
2638
|
+
function ifToCondition(ifSchema) {
|
|
2639
|
+
const empty = { field: "", operator: "=", value: "" };
|
|
2640
|
+
if (!ifSchema) return empty;
|
|
2641
|
+
const props = ifSchema.properties ?? {};
|
|
2642
|
+
const field = Object.keys(props)[0] ?? ifSchema.required?.[0] ?? "";
|
|
2643
|
+
if (!field) return empty;
|
|
2644
|
+
const constraint = props[field];
|
|
2645
|
+
if (!constraint) return { field, operator: "exists", value: "" };
|
|
2646
|
+
const { operator, value } = constraintToOperator(constraint);
|
|
2647
|
+
return { field, operator, value };
|
|
2648
|
+
}
|
|
2649
|
+
|
|
2650
|
+
// src/inspector/OperatorValueInput.tsx
|
|
2651
|
+
import { Input as Input2 } from "@petrarca/sonnet-ui";
|
|
1317
2652
|
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
2653
|
+
function OperatorValueInput({
|
|
2654
|
+
id,
|
|
2655
|
+
cacheKey,
|
|
2656
|
+
value,
|
|
2657
|
+
onCommit,
|
|
2658
|
+
disabled
|
|
2659
|
+
}) {
|
|
2660
|
+
return /* @__PURE__ */ jsxs3("div", { className: "space-y-1", children: [
|
|
2661
|
+
/* @__PURE__ */ jsx4("label", { htmlFor: id, className: "text-sm font-medium", children: "Value" }),
|
|
2662
|
+
/* @__PURE__ */ jsx4(
|
|
2663
|
+
Input2,
|
|
2664
|
+
{
|
|
2665
|
+
id,
|
|
2666
|
+
defaultValue: value,
|
|
2667
|
+
onBlur: (e) => onCommit(e.target.value),
|
|
2668
|
+
disabled,
|
|
2669
|
+
placeholder: "Value to compare"
|
|
2670
|
+
},
|
|
2671
|
+
cacheKey
|
|
2672
|
+
)
|
|
2673
|
+
] });
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2676
|
+
// src/inspector/RuleFields.tsx
|
|
2677
|
+
import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
2678
|
+
function ruleToForm(rule) {
|
|
2679
|
+
if (!rule) return { effect: "", scope: "", operator: "=", value: "" };
|
|
2680
|
+
const { operator, value } = constraintToOperator(rule.condition.schema);
|
|
2681
|
+
return { effect: rule.effect, scope: rule.condition.scope, operator, value };
|
|
2682
|
+
}
|
|
2683
|
+
function formToRule(form, scopeType) {
|
|
2684
|
+
if (!form.effect || !form.scope) return void 0;
|
|
2685
|
+
return {
|
|
2686
|
+
effect: form.effect,
|
|
2687
|
+
condition: {
|
|
2688
|
+
scope: form.scope,
|
|
2689
|
+
schema: operatorToConstraint(form.operator, form.value, scopeType)
|
|
2690
|
+
}
|
|
2691
|
+
};
|
|
2692
|
+
}
|
|
2693
|
+
var BOOLEAN_VALUE_OPTIONS = [
|
|
2694
|
+
{ value: "true", label: "true" },
|
|
2695
|
+
{ value: "false", label: "false" }
|
|
2696
|
+
];
|
|
2697
|
+
function RuleFields({
|
|
2698
|
+
rule,
|
|
2699
|
+
scopes,
|
|
2700
|
+
effects,
|
|
2701
|
+
idPrefix,
|
|
2702
|
+
disabled = false,
|
|
2703
|
+
onChange
|
|
2704
|
+
}) {
|
|
2705
|
+
const [form, setForm] = useState3(() => ruleToForm(rule));
|
|
2706
|
+
const seed = JSON.stringify(rule ?? null);
|
|
2707
|
+
useEffect2(() => {
|
|
2708
|
+
setForm(ruleToForm(JSON.parse(seed) ?? void 0));
|
|
2709
|
+
}, [seed]);
|
|
2710
|
+
const scopeType = useMemo4(
|
|
2711
|
+
() => scopes.find((s) => s.pointer === form.scope)?.type,
|
|
2712
|
+
[scopes, form.scope]
|
|
2713
|
+
);
|
|
2714
|
+
const commit = (next) => {
|
|
2715
|
+
setForm(next);
|
|
2716
|
+
const nextType = scopes.find((s) => s.pointer === next.scope)?.type;
|
|
2717
|
+
const built = formToRule(next, nextType);
|
|
2718
|
+
if (built || next.effect === "") onChange(built);
|
|
2719
|
+
};
|
|
2720
|
+
const scopeOptions = useMemo4(
|
|
2721
|
+
() => scopes.map((s) => ({ value: s.pointer, label: s.pointer.slice(1) })),
|
|
2722
|
+
[scopes]
|
|
2723
|
+
);
|
|
2724
|
+
const needsValue = form.operator !== "exists";
|
|
2725
|
+
const isBoolean = scopeType === "boolean";
|
|
2726
|
+
return /* @__PURE__ */ jsxs4("div", { className: "space-y-3", children: [
|
|
2727
|
+
/* @__PURE__ */ jsx5(
|
|
2728
|
+
FormSelect2,
|
|
2729
|
+
{
|
|
2730
|
+
id: `${idPrefix}-effect`,
|
|
2731
|
+
name: "effect",
|
|
2732
|
+
label: "Effect",
|
|
2733
|
+
options: effects,
|
|
2734
|
+
value: form.effect,
|
|
2735
|
+
onChange: (v) => commit({ ...form, effect: v ?? "" }),
|
|
2736
|
+
clearable: false,
|
|
2737
|
+
disabled
|
|
2738
|
+
}
|
|
2739
|
+
),
|
|
2740
|
+
form.effect !== "" && /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
2741
|
+
/* @__PURE__ */ jsx5(
|
|
2742
|
+
FormSelect2,
|
|
2743
|
+
{
|
|
2744
|
+
id: `${idPrefix}-scope`,
|
|
2745
|
+
name: "scope",
|
|
2746
|
+
label: "When field",
|
|
2747
|
+
options: scopeOptions,
|
|
2748
|
+
value: form.scope || null,
|
|
2749
|
+
onChange: (v) => commit({ ...form, scope: v ?? "" }),
|
|
2750
|
+
placeholder: "Select a field...",
|
|
2751
|
+
clearable: false,
|
|
2752
|
+
disabled
|
|
2753
|
+
}
|
|
2754
|
+
),
|
|
2755
|
+
/* @__PURE__ */ jsx5(
|
|
2756
|
+
FormSelect2,
|
|
2757
|
+
{
|
|
2758
|
+
id: `${idPrefix}-operator`,
|
|
2759
|
+
name: "operator",
|
|
2760
|
+
label: "Operator",
|
|
2761
|
+
options: OPERATOR_OPTIONS,
|
|
2762
|
+
value: form.operator,
|
|
2763
|
+
onChange: (v) => commit({ ...form, operator: v ?? "=" }),
|
|
2764
|
+
clearable: false,
|
|
2765
|
+
disabled
|
|
2766
|
+
}
|
|
2767
|
+
),
|
|
2768
|
+
needsValue && (isBoolean ? /* @__PURE__ */ jsx5(
|
|
2769
|
+
FormSelect2,
|
|
2770
|
+
{
|
|
2771
|
+
id: `${idPrefix}-value`,
|
|
2772
|
+
name: "value",
|
|
2773
|
+
label: "Value",
|
|
2774
|
+
options: BOOLEAN_VALUE_OPTIONS,
|
|
2775
|
+
value: form.value === "true" ? "true" : "false",
|
|
2776
|
+
onChange: (v) => commit({ ...form, value: v ?? "false" }),
|
|
2777
|
+
clearable: false,
|
|
2778
|
+
disabled: disabled || !form.scope
|
|
2779
|
+
}
|
|
2780
|
+
) : /* @__PURE__ */ jsx5(
|
|
2781
|
+
OperatorValueInput,
|
|
2782
|
+
{
|
|
2783
|
+
id: `${idPrefix}-value`,
|
|
2784
|
+
cacheKey: `${form.scope}:${form.operator}`,
|
|
2785
|
+
value: form.value,
|
|
2786
|
+
onCommit: (v) => commit({ ...form, value: v }),
|
|
2787
|
+
disabled: disabled || !form.scope
|
|
2788
|
+
}
|
|
2789
|
+
))
|
|
2790
|
+
] })
|
|
2791
|
+
] });
|
|
2792
|
+
}
|
|
2793
|
+
|
|
2794
|
+
// src/inspector/ruleEffects.ts
|
|
2795
|
+
var VISIBILITY_EFFECT_OPTIONS = [
|
|
2796
|
+
{ value: "", label: "None (always shown)" },
|
|
2797
|
+
{ value: "show", label: "Show when" },
|
|
2798
|
+
{ value: "hide", label: "Hide when" }
|
|
2799
|
+
];
|
|
2800
|
+
var FIELD_EFFECT_OPTIONS = [
|
|
2801
|
+
{ value: "", label: "None (always visible)" },
|
|
2802
|
+
{ value: "show", label: "Show when" },
|
|
2803
|
+
{ value: "hide", label: "Hide when" },
|
|
2804
|
+
{ value: "enable", label: "Enable when" },
|
|
2805
|
+
{ value: "disable", label: "Disable when" }
|
|
2806
|
+
];
|
|
2807
|
+
|
|
2808
|
+
// src/designers/FormLayoutDesigner.tsx
|
|
2809
|
+
import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1318
2810
|
var DRAG_MIME = "application/x-sonnet-property";
|
|
1319
2811
|
function orderedKeysOf(properties, order) {
|
|
1320
2812
|
const keys = Object.keys(properties);
|
|
@@ -1323,21 +2815,43 @@ function orderedKeysOf(properties, order) {
|
|
|
1323
2815
|
return [...inOrder, ...keys.filter((k) => !inOrder.includes(k))];
|
|
1324
2816
|
}
|
|
1325
2817
|
function scopeTarget(schema, scopePath) {
|
|
1326
|
-
const
|
|
1327
|
-
if (!
|
|
1328
|
-
return
|
|
2818
|
+
const prop2 = getProperty(schema, scopePath);
|
|
2819
|
+
if (!prop2) return void 0;
|
|
2820
|
+
return prop2.type === "array" ? prop2.items : prop2;
|
|
2821
|
+
}
|
|
2822
|
+
function scopeContainer(schema, uiSchema, scopePath) {
|
|
2823
|
+
if (scopePath.length === 0) {
|
|
2824
|
+
return {
|
|
2825
|
+
container: schema,
|
|
2826
|
+
layout: uiSchema["x-ui-layout"]
|
|
2827
|
+
};
|
|
2828
|
+
}
|
|
2829
|
+
const container = scopeTarget(schema, scopePath);
|
|
2830
|
+
return {
|
|
2831
|
+
container,
|
|
2832
|
+
layout: container?.["x-ui-layout"]
|
|
2833
|
+
};
|
|
1329
2834
|
}
|
|
1330
2835
|
function resolveScope(schema, uiSchema, scopePath) {
|
|
1331
|
-
const container
|
|
1332
|
-
const
|
|
1333
|
-
const isArray = scopeIsArray(schema, scopePath);
|
|
2836
|
+
const { container, layout } = scopeContainer(schema, uiSchema, scopePath);
|
|
2837
|
+
const props = container?.properties ?? {};
|
|
1334
2838
|
return {
|
|
1335
|
-
keys: orderedKeysOf(
|
|
2839
|
+
keys: orderedKeysOf(props, container?.["x-ui-order"]),
|
|
2840
|
+
props,
|
|
1336
2841
|
rows: layout?.rows ?? [],
|
|
2842
|
+
groups: layout?.groups ?? [],
|
|
1337
2843
|
layout,
|
|
1338
|
-
isArray
|
|
2844
|
+
isArray: scopeIsArray(schema, scopePath)
|
|
1339
2845
|
};
|
|
1340
2846
|
}
|
|
2847
|
+
function ownsField(rows, key) {
|
|
2848
|
+
return rows.some((r) => r.fields.includes(key));
|
|
2849
|
+
}
|
|
2850
|
+
function rowTarget(grouped, groups, groupIndex, flatRows) {
|
|
2851
|
+
const rows = grouped ? groups[groupIndex]?.rows ?? [] : flatRows;
|
|
2852
|
+
const all = grouped ? groups.flatMap((t) => t.rows) : flatRows;
|
|
2853
|
+
return { rows, assigned: new Set(all.flatMap((r) => r.fields)) };
|
|
2854
|
+
}
|
|
1341
2855
|
function scopeIsArray(schema, scopePath) {
|
|
1342
2856
|
if (scopePath.length === 0) return false;
|
|
1343
2857
|
return getProperty(schema, scopePath)?.type === "array";
|
|
@@ -1377,9 +2891,9 @@ function ArrayItemLayoutBar({
|
|
|
1377
2891
|
readOnly,
|
|
1378
2892
|
onChange
|
|
1379
2893
|
}) {
|
|
1380
|
-
return /* @__PURE__ */
|
|
1381
|
-
/* @__PURE__ */
|
|
1382
|
-
/* @__PURE__ */
|
|
2894
|
+
return /* @__PURE__ */ jsxs5("div", { className: "shrink-0 rounded-lg border p-3", children: [
|
|
2895
|
+
/* @__PURE__ */ jsx6("p", { className: "heading-meta mb-2", children: "Array item layout" }),
|
|
2896
|
+
/* @__PURE__ */ jsx6(
|
|
1383
2897
|
JsonSchemaFormRenderer2,
|
|
1384
2898
|
{
|
|
1385
2899
|
schema: SCALAR_META_SCHEMA,
|
|
@@ -1396,23 +2910,37 @@ function ArrayItemLayoutBar({
|
|
|
1396
2910
|
function FormLayoutDesigner({
|
|
1397
2911
|
state
|
|
1398
2912
|
}) {
|
|
1399
|
-
const scopes =
|
|
1400
|
-
const [scopePath, setScopePath] =
|
|
1401
|
-
const scopeKey = scopePath
|
|
1402
|
-
const activeScope = scopes.find((s) => s.path
|
|
2913
|
+
const scopes = useMemo5(() => layoutScopes(state.schema), [state.schema]);
|
|
2914
|
+
const [scopePath, setScopePath] = useState4([]);
|
|
2915
|
+
const scopeKey = pathKey(scopePath);
|
|
2916
|
+
const activeScope = scopes.find((s) => pathKey(s.path) === scopeKey) ?? scopes[0];
|
|
1403
2917
|
const effectivePath = activeScope.path;
|
|
1404
2918
|
const {
|
|
1405
2919
|
keys: allKeys,
|
|
1406
|
-
|
|
2920
|
+
props: scopeProps,
|
|
2921
|
+
rows: flatRows,
|
|
2922
|
+
groups,
|
|
1407
2923
|
layout,
|
|
1408
2924
|
isArray
|
|
1409
2925
|
} = resolveScope(state.schema, state.uiSchema, effectivePath);
|
|
1410
|
-
const
|
|
2926
|
+
const grouped = groups.length > 0;
|
|
2927
|
+
const [activeGroup, setActiveGroup] = useState4(0);
|
|
2928
|
+
const groupIndex = grouped ? Math.min(activeGroup, groups.length - 1) : 0;
|
|
2929
|
+
const { rows, assigned } = rowTarget(grouped, groups, groupIndex, flatRows);
|
|
1411
2930
|
const available = allKeys.filter((k) => !assigned.has(k));
|
|
1412
|
-
const
|
|
1413
|
-
const
|
|
2931
|
+
const inOtherGroups = grouped ? allKeys.filter((k) => assigned.has(k) && !ownsField(rows, k)) : [];
|
|
2932
|
+
const [dragKey, setDragKey] = useState4(null);
|
|
2933
|
+
const commit = makeCommitRows(state, effectivePath, grouped, groupIndex);
|
|
1414
2934
|
const place = (key, rowIndex) => {
|
|
1415
2935
|
if (!key) return;
|
|
2936
|
+
if (grouped) {
|
|
2937
|
+
state.placeFieldInGroup(
|
|
2938
|
+
effectivePath,
|
|
2939
|
+
key,
|
|
2940
|
+
rowIndex === null ? null : { groupIndex, rowIndex }
|
|
2941
|
+
);
|
|
2942
|
+
return;
|
|
2943
|
+
}
|
|
1416
2944
|
const next = rows.map((r) => ({
|
|
1417
2945
|
...r,
|
|
1418
2946
|
fields: r.fields.filter((f) => f !== key)
|
|
@@ -1466,26 +2994,57 @@ function FormLayoutDesigner({
|
|
|
1466
2994
|
});
|
|
1467
2995
|
commit(next);
|
|
1468
2996
|
};
|
|
2997
|
+
const scopeOptions = allKeys.map((k) => ({
|
|
2998
|
+
pointer: `/${k}`,
|
|
2999
|
+
type: scopeProps[k]?.type
|
|
3000
|
+
}));
|
|
3001
|
+
const groupOps = makeGroupOps(
|
|
3002
|
+
state,
|
|
3003
|
+
effectivePath,
|
|
3004
|
+
groups.length,
|
|
3005
|
+
groupIndex,
|
|
3006
|
+
setActiveGroup
|
|
3007
|
+
);
|
|
1469
3008
|
const readOnly = state.readOnly;
|
|
1470
|
-
return /* @__PURE__ */
|
|
1471
|
-
/* @__PURE__ */
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
3009
|
+
return /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-3 h-full min-h-0", children: [
|
|
3010
|
+
/* @__PURE__ */ jsx6(
|
|
3011
|
+
LayoutToolbar,
|
|
3012
|
+
{
|
|
3013
|
+
scopes,
|
|
3014
|
+
scopePath: effectivePath,
|
|
3015
|
+
onScope: setScopePath,
|
|
3016
|
+
grouped,
|
|
3017
|
+
variant: layout?.variant,
|
|
3018
|
+
readOnly,
|
|
3019
|
+
onMode: groupOps.setMode,
|
|
3020
|
+
onVariant: (v) => state.setScopedLayoutVariant(effectivePath, v)
|
|
3021
|
+
}
|
|
3022
|
+
),
|
|
3023
|
+
grouped && /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
3024
|
+
/* @__PURE__ */ jsx6(
|
|
3025
|
+
GroupBar,
|
|
1475
3026
|
{
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
clearable: false
|
|
3027
|
+
groups,
|
|
3028
|
+
activeIndex: groupIndex,
|
|
3029
|
+
readOnly,
|
|
3030
|
+
onSelect: setActiveGroup,
|
|
3031
|
+
onAdd: groupOps.add,
|
|
3032
|
+
onRename: groupOps.rename,
|
|
3033
|
+
onRemove: groupOps.remove,
|
|
3034
|
+
onMove: groupOps.move
|
|
1485
3035
|
}
|
|
1486
|
-
)
|
|
3036
|
+
),
|
|
3037
|
+
/* @__PURE__ */ jsx6(
|
|
3038
|
+
GroupSettings,
|
|
3039
|
+
{
|
|
3040
|
+
group: groups[groupIndex],
|
|
3041
|
+
scopes: scopeOptions,
|
|
3042
|
+
readOnly,
|
|
3043
|
+
onRule: (rule) => state.setLayoutGroupRule(effectivePath, groupIndex, rule)
|
|
3044
|
+
}
|
|
3045
|
+
)
|
|
1487
3046
|
] }),
|
|
1488
|
-
isArray && /* @__PURE__ */
|
|
3047
|
+
isArray && /* @__PURE__ */ jsx6(
|
|
1489
3048
|
ArrayItemLayoutBar,
|
|
1490
3049
|
{
|
|
1491
3050
|
layout,
|
|
@@ -1493,38 +3052,24 @@ function FormLayoutDesigner({
|
|
|
1493
3052
|
onChange: (opts) => state.setScopedLayoutOptions(effectivePath, opts)
|
|
1494
3053
|
}
|
|
1495
3054
|
),
|
|
1496
|
-
/* @__PURE__ */
|
|
1497
|
-
/* @__PURE__ */
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
readOnly,
|
|
1515
|
-
onDragStart: () => setDragKey(key),
|
|
1516
|
-
onDragEnd: () => setDragKey(null)
|
|
1517
|
-
},
|
|
1518
|
-
key
|
|
1519
|
-
))
|
|
1520
|
-
}
|
|
1521
|
-
),
|
|
1522
|
-
/* @__PURE__ */ jsx4("p", { className: "text-xs text-muted-foreground mt-2", children: "Unplaced properties render stacked below the rows." })
|
|
1523
|
-
] }),
|
|
1524
|
-
/* @__PURE__ */ jsxs3("div", { className: "flex-1 min-w-0 flex flex-col min-h-0", children: [
|
|
1525
|
-
/* @__PURE__ */ jsxs3("div", { className: "mb-2 flex items-center gap-2", children: [
|
|
1526
|
-
/* @__PURE__ */ jsx4("p", { className: "heading-meta", children: "Rows" }),
|
|
1527
|
-
!readOnly && /* @__PURE__ */ jsx4("span", { className: "ml-auto", children: /* @__PURE__ */ jsxs3(
|
|
3055
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex gap-4 flex-1 min-h-0", children: [
|
|
3056
|
+
/* @__PURE__ */ jsx6(
|
|
3057
|
+
AvailablePool,
|
|
3058
|
+
{
|
|
3059
|
+
available,
|
|
3060
|
+
inOtherGroups,
|
|
3061
|
+
grouped,
|
|
3062
|
+
readOnly,
|
|
3063
|
+
dragActive: !!dragKey,
|
|
3064
|
+
onDropToPool: (key) => place(key, null),
|
|
3065
|
+
onDragStartKey: setDragKey,
|
|
3066
|
+
onDragEndKey: () => setDragKey(null)
|
|
3067
|
+
}
|
|
3068
|
+
),
|
|
3069
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex-1 min-w-0 flex flex-col min-h-0", children: [
|
|
3070
|
+
/* @__PURE__ */ jsxs5("div", { className: "mb-2 flex items-center gap-2", children: [
|
|
3071
|
+
/* @__PURE__ */ jsx6("p", { className: "heading-meta", children: rowsHeading(grouped, groups, groupIndex) }),
|
|
3072
|
+
!readOnly && /* @__PURE__ */ jsx6("span", { className: "ml-auto", children: /* @__PURE__ */ jsxs5(
|
|
1528
3073
|
Button,
|
|
1529
3074
|
{
|
|
1530
3075
|
size: "sm",
|
|
@@ -1532,13 +3077,13 @@ function FormLayoutDesigner({
|
|
|
1532
3077
|
className: "gap-1.5",
|
|
1533
3078
|
onClick: addRow,
|
|
1534
3079
|
children: [
|
|
1535
|
-
/* @__PURE__ */
|
|
3080
|
+
/* @__PURE__ */ jsx6(Plus2, { className: "h-4 w-4" }),
|
|
1536
3081
|
"Add row"
|
|
1537
3082
|
]
|
|
1538
3083
|
}
|
|
1539
3084
|
) })
|
|
1540
3085
|
] }),
|
|
1541
|
-
/* @__PURE__ */
|
|
3086
|
+
/* @__PURE__ */ jsx6("div", { className: "flex-1 min-h-0 overflow-auto space-y-3", children: rows.length === 0 ? /* @__PURE__ */ jsx6("p", { className: "text-sm text-muted-foreground", children: "No rows yet. Add a row, then drag properties into it." }) : rows.map((row, i) => /* @__PURE__ */ jsx6(
|
|
1542
3087
|
RowCard,
|
|
1543
3088
|
{
|
|
1544
3089
|
row,
|
|
@@ -1564,6 +3109,352 @@ function FormLayoutDesigner({
|
|
|
1564
3109
|
] })
|
|
1565
3110
|
] });
|
|
1566
3111
|
}
|
|
3112
|
+
function makeCommitRows(state, scopePath, grouped, groupIndex) {
|
|
3113
|
+
if (grouped) {
|
|
3114
|
+
return (rows) => state.setScopedGroupRows(scopePath, groupIndex, rows);
|
|
3115
|
+
}
|
|
3116
|
+
return (rows) => state.setScopedLayoutRows(scopePath, rows);
|
|
3117
|
+
}
|
|
3118
|
+
function rowsHeading(grouped, groups, groupIndex) {
|
|
3119
|
+
if (!grouped) return "Rows";
|
|
3120
|
+
return `Rows in ${groups[groupIndex]?.label ?? ""}`;
|
|
3121
|
+
}
|
|
3122
|
+
var VARIANT_OPTIONS = [
|
|
3123
|
+
{ value: "tabs", label: "Tabs" },
|
|
3124
|
+
{ value: "stepper", label: "Stepper" },
|
|
3125
|
+
{ value: "sections", label: "Sections" }
|
|
3126
|
+
];
|
|
3127
|
+
function makeGroupOps(state, scopePath, groupCount, activeIndex, setActiveGroup) {
|
|
3128
|
+
return {
|
|
3129
|
+
add: () => {
|
|
3130
|
+
state.addLayoutGroup(scopePath);
|
|
3131
|
+
setActiveGroup(groupCount);
|
|
3132
|
+
},
|
|
3133
|
+
rename: (index, label) => state.renameLayoutGroup(scopePath, index, label),
|
|
3134
|
+
move: (index, direction) => {
|
|
3135
|
+
state.moveLayoutGroup(scopePath, index, direction);
|
|
3136
|
+
if (index === activeIndex) setActiveGroup(activeIndex + direction);
|
|
3137
|
+
},
|
|
3138
|
+
remove: (index) => {
|
|
3139
|
+
state.removeLayoutGroup(scopePath, index);
|
|
3140
|
+
if (index <= activeIndex && activeIndex > 0)
|
|
3141
|
+
setActiveGroup(activeIndex - 1);
|
|
3142
|
+
},
|
|
3143
|
+
setMode: (mode) => {
|
|
3144
|
+
state.setScopedLayoutMode(scopePath, mode);
|
|
3145
|
+
setActiveGroup(0);
|
|
3146
|
+
}
|
|
3147
|
+
};
|
|
3148
|
+
}
|
|
3149
|
+
function AvailablePool({
|
|
3150
|
+
available,
|
|
3151
|
+
inOtherGroups,
|
|
3152
|
+
grouped,
|
|
3153
|
+
readOnly,
|
|
3154
|
+
dragActive,
|
|
3155
|
+
onDropToPool,
|
|
3156
|
+
onDragStartKey,
|
|
3157
|
+
onDragEndKey
|
|
3158
|
+
}) {
|
|
3159
|
+
const empty = available.length === 0 && inOtherGroups.length === 0;
|
|
3160
|
+
return /* @__PURE__ */ jsxs5("div", { className: "w-56 shrink-0 flex flex-col min-h-0", children: [
|
|
3161
|
+
/* @__PURE__ */ jsx6("p", { className: "heading-meta mb-2", children: "Available" }),
|
|
3162
|
+
/* @__PURE__ */ jsxs5(
|
|
3163
|
+
"div",
|
|
3164
|
+
{
|
|
3165
|
+
className: "flex-1 min-h-0 overflow-auto rounded-lg border p-2 space-y-1 bg-muted/20",
|
|
3166
|
+
onDragOver: (e) => {
|
|
3167
|
+
if (dragActive) e.preventDefault();
|
|
3168
|
+
},
|
|
3169
|
+
onDrop: (e) => {
|
|
3170
|
+
const key = e.dataTransfer.getData(DRAG_MIME);
|
|
3171
|
+
if (key) onDropToPool(key);
|
|
3172
|
+
},
|
|
3173
|
+
children: [
|
|
3174
|
+
empty ? /* @__PURE__ */ jsx6("p", { className: "text-xs text-muted-foreground p-1", children: grouped ? "All properties are placed in groups." : "All properties are placed in rows." }) : available.map((key) => /* @__PURE__ */ jsx6(
|
|
3175
|
+
Chip,
|
|
3176
|
+
{
|
|
3177
|
+
label: key,
|
|
3178
|
+
readOnly,
|
|
3179
|
+
onDragStart: () => onDragStartKey(key),
|
|
3180
|
+
onDragEnd: onDragEndKey
|
|
3181
|
+
},
|
|
3182
|
+
key
|
|
3183
|
+
)),
|
|
3184
|
+
inOtherGroups.length > 0 && /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
3185
|
+
/* @__PURE__ */ jsx6("p", { className: "pt-2 pb-1 text-[11px] uppercase tracking-wide text-muted-foreground/70", children: "In other groups" }),
|
|
3186
|
+
inOtherGroups.map((key) => /* @__PURE__ */ jsx6(
|
|
3187
|
+
Chip,
|
|
3188
|
+
{
|
|
3189
|
+
label: key,
|
|
3190
|
+
readOnly,
|
|
3191
|
+
muted: true,
|
|
3192
|
+
onDragStart: () => onDragStartKey(key),
|
|
3193
|
+
onDragEnd: onDragEndKey
|
|
3194
|
+
},
|
|
3195
|
+
key
|
|
3196
|
+
))
|
|
3197
|
+
] })
|
|
3198
|
+
]
|
|
3199
|
+
}
|
|
3200
|
+
),
|
|
3201
|
+
/* @__PURE__ */ jsx6("p", { className: "text-xs text-muted-foreground mt-2", children: grouped ? "Drag from \u201CIn other groups\u201D to move a property here. Unplaced properties render below the groups." : "Unplaced properties render stacked below the rows." })
|
|
3202
|
+
] });
|
|
3203
|
+
}
|
|
3204
|
+
function LayoutToolbar({
|
|
3205
|
+
scopes,
|
|
3206
|
+
scopePath,
|
|
3207
|
+
onScope,
|
|
3208
|
+
grouped,
|
|
3209
|
+
variant,
|
|
3210
|
+
readOnly,
|
|
3211
|
+
onMode,
|
|
3212
|
+
onVariant
|
|
3213
|
+
}) {
|
|
3214
|
+
return /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2 shrink-0", children: [
|
|
3215
|
+
/* @__PURE__ */ jsx6("label", { className: "heading-meta", children: "Scope" }),
|
|
3216
|
+
/* @__PURE__ */ jsx6("div", { className: "w-64", children: /* @__PURE__ */ jsx6(
|
|
3217
|
+
FormSelect3,
|
|
3218
|
+
{
|
|
3219
|
+
id: "layout-scope",
|
|
3220
|
+
name: "scope",
|
|
3221
|
+
value: pathKey(scopePath),
|
|
3222
|
+
options: scopes.map((s) => ({
|
|
3223
|
+
value: pathKey(s.path),
|
|
3224
|
+
label: s.label
|
|
3225
|
+
})),
|
|
3226
|
+
onChange: (v) => onScope(v ? parsePathKey(v) : []),
|
|
3227
|
+
clearable: false
|
|
3228
|
+
}
|
|
3229
|
+
) }),
|
|
3230
|
+
/* @__PURE__ */ jsx6("label", { className: "heading-meta ml-4", children: "Group by" }),
|
|
3231
|
+
/* @__PURE__ */ jsx6(
|
|
3232
|
+
ModeToggle,
|
|
3233
|
+
{
|
|
3234
|
+
mode: grouped ? "groups" : "rows",
|
|
3235
|
+
readOnly,
|
|
3236
|
+
onChange: onMode
|
|
3237
|
+
}
|
|
3238
|
+
),
|
|
3239
|
+
grouped && /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
3240
|
+
/* @__PURE__ */ jsx6("label", { className: "heading-meta ml-4", children: "Show as" }),
|
|
3241
|
+
/* @__PURE__ */ jsx6("div", { className: "w-36", children: /* @__PURE__ */ jsx6(
|
|
3242
|
+
FormSelect3,
|
|
3243
|
+
{
|
|
3244
|
+
id: "layout-variant",
|
|
3245
|
+
name: "variant",
|
|
3246
|
+
value: variant ?? "tabs",
|
|
3247
|
+
options: VARIANT_OPTIONS,
|
|
3248
|
+
onChange: (v) => onVariant(v ?? "tabs"),
|
|
3249
|
+
clearable: false,
|
|
3250
|
+
disabled: readOnly
|
|
3251
|
+
}
|
|
3252
|
+
) })
|
|
3253
|
+
] })
|
|
3254
|
+
] });
|
|
3255
|
+
}
|
|
3256
|
+
function ModeToggle({
|
|
3257
|
+
mode,
|
|
3258
|
+
readOnly,
|
|
3259
|
+
onChange
|
|
3260
|
+
}) {
|
|
3261
|
+
return /* @__PURE__ */ jsx6("div", { className: "inline-flex rounded-md border p-0.5", "aria-label": "Group by", children: ["rows", "groups"].map((m) => /* @__PURE__ */ jsx6(
|
|
3262
|
+
"button",
|
|
3263
|
+
{
|
|
3264
|
+
type: "button",
|
|
3265
|
+
disabled: readOnly,
|
|
3266
|
+
"aria-pressed": mode === m,
|
|
3267
|
+
onClick: () => onChange(m),
|
|
3268
|
+
className: cn2(
|
|
3269
|
+
"rounded px-2 py-0.5 text-xs capitalize disabled:opacity-50",
|
|
3270
|
+
mode === m ? "bg-accent text-accent-foreground font-medium" : "text-muted-foreground hover:text-foreground"
|
|
3271
|
+
),
|
|
3272
|
+
children: m
|
|
3273
|
+
},
|
|
3274
|
+
m
|
|
3275
|
+
)) });
|
|
3276
|
+
}
|
|
3277
|
+
function GroupSettings({
|
|
3278
|
+
group,
|
|
3279
|
+
scopes,
|
|
3280
|
+
readOnly,
|
|
3281
|
+
onRule
|
|
3282
|
+
}) {
|
|
3283
|
+
const [open, setOpen] = useState4(false);
|
|
3284
|
+
if (!group) return null;
|
|
3285
|
+
const hasRule = !!group.rule;
|
|
3286
|
+
return /* @__PURE__ */ jsxs5("div", { className: "shrink-0 rounded-lg border px-3 py-2", children: [
|
|
3287
|
+
/* @__PURE__ */ jsxs5(
|
|
3288
|
+
"button",
|
|
3289
|
+
{
|
|
3290
|
+
type: "button",
|
|
3291
|
+
className: "flex w-full items-center gap-1 heading-meta",
|
|
3292
|
+
onClick: () => setOpen((v) => !v),
|
|
3293
|
+
"aria-expanded": open,
|
|
3294
|
+
children: [
|
|
3295
|
+
open ? /* @__PURE__ */ jsx6(ChevronDown2, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx6(ChevronRight2, { className: "h-3 w-3" }),
|
|
3296
|
+
"Visibility of ",
|
|
3297
|
+
group.label,
|
|
3298
|
+
/* @__PURE__ */ jsx6("span", { className: "ml-1 normal-case text-muted-foreground/70", children: hasRule ? "conditional" : "always shown" })
|
|
3299
|
+
]
|
|
3300
|
+
}
|
|
3301
|
+
),
|
|
3302
|
+
open && /* @__PURE__ */ jsx6("div", { className: "mt-3 max-w-sm", children: /* @__PURE__ */ jsx6(
|
|
3303
|
+
RuleFields,
|
|
3304
|
+
{
|
|
3305
|
+
rule: group.rule,
|
|
3306
|
+
scopes,
|
|
3307
|
+
effects: VISIBILITY_EFFECT_OPTIONS,
|
|
3308
|
+
idPrefix: "group-rule",
|
|
3309
|
+
disabled: readOnly,
|
|
3310
|
+
onChange: (rule) => onRule(rule)
|
|
3311
|
+
}
|
|
3312
|
+
) })
|
|
3313
|
+
] });
|
|
3314
|
+
}
|
|
3315
|
+
function GroupBar({
|
|
3316
|
+
groups,
|
|
3317
|
+
activeIndex,
|
|
3318
|
+
readOnly,
|
|
3319
|
+
onSelect,
|
|
3320
|
+
onAdd,
|
|
3321
|
+
onRename,
|
|
3322
|
+
onRemove,
|
|
3323
|
+
onMove
|
|
3324
|
+
}) {
|
|
3325
|
+
return /* @__PURE__ */ jsxs5(
|
|
3326
|
+
"div",
|
|
3327
|
+
{
|
|
3328
|
+
role: "tablist",
|
|
3329
|
+
"aria-label": "Layout groups",
|
|
3330
|
+
className: "shrink-0 flex items-center gap-1 rounded-lg border p-1.5 overflow-x-auto",
|
|
3331
|
+
children: [
|
|
3332
|
+
groups.map((group, i) => /* @__PURE__ */ jsx6(
|
|
3333
|
+
GroupChip,
|
|
3334
|
+
{
|
|
3335
|
+
group,
|
|
3336
|
+
active: i === activeIndex,
|
|
3337
|
+
readOnly,
|
|
3338
|
+
onSelect: () => onSelect(i),
|
|
3339
|
+
onRename: (label) => onRename(i, label),
|
|
3340
|
+
onRemove: () => onRemove(i),
|
|
3341
|
+
onMoveLeft: i > 0 ? () => onMove(i, -1) : void 0,
|
|
3342
|
+
onMoveRight: i < groups.length - 1 ? () => onMove(i, 1) : void 0
|
|
3343
|
+
},
|
|
3344
|
+
`${i}:${group.label}`
|
|
3345
|
+
)),
|
|
3346
|
+
!readOnly && /* @__PURE__ */ jsxs5(
|
|
3347
|
+
Button,
|
|
3348
|
+
{
|
|
3349
|
+
size: "sm",
|
|
3350
|
+
variant: "outline",
|
|
3351
|
+
className: "gap-1.5 shrink-0",
|
|
3352
|
+
onClick: onAdd,
|
|
3353
|
+
children: [
|
|
3354
|
+
/* @__PURE__ */ jsx6(Plus2, { className: "h-4 w-4" }),
|
|
3355
|
+
"Add group"
|
|
3356
|
+
]
|
|
3357
|
+
}
|
|
3358
|
+
)
|
|
3359
|
+
]
|
|
3360
|
+
}
|
|
3361
|
+
);
|
|
3362
|
+
}
|
|
3363
|
+
function GroupChip({
|
|
3364
|
+
group,
|
|
3365
|
+
active,
|
|
3366
|
+
readOnly,
|
|
3367
|
+
onSelect,
|
|
3368
|
+
onRename,
|
|
3369
|
+
onRemove,
|
|
3370
|
+
onMoveLeft,
|
|
3371
|
+
onMoveRight
|
|
3372
|
+
}) {
|
|
3373
|
+
const [editing, setEditing] = useState4(false);
|
|
3374
|
+
if (editing) {
|
|
3375
|
+
return /* @__PURE__ */ jsx6(
|
|
3376
|
+
Input3,
|
|
3377
|
+
{
|
|
3378
|
+
autoFocus: true,
|
|
3379
|
+
defaultValue: group.label,
|
|
3380
|
+
className: "h-7 w-32 text-sm",
|
|
3381
|
+
onBlur: (e) => {
|
|
3382
|
+
const next = e.target.value.trim();
|
|
3383
|
+
if (next && next !== group.label) onRename(next);
|
|
3384
|
+
setEditing(false);
|
|
3385
|
+
},
|
|
3386
|
+
onKeyDown: (e) => {
|
|
3387
|
+
if (e.key === "Enter") e.currentTarget.blur();
|
|
3388
|
+
if (e.key === "Escape") setEditing(false);
|
|
3389
|
+
}
|
|
3390
|
+
}
|
|
3391
|
+
);
|
|
3392
|
+
}
|
|
3393
|
+
const fieldCount = group.rows.reduce((n, r) => n + r.fields.length, 0);
|
|
3394
|
+
return /* @__PURE__ */ jsxs5(
|
|
3395
|
+
"span",
|
|
3396
|
+
{
|
|
3397
|
+
className: cn2(
|
|
3398
|
+
"group/group inline-flex shrink-0 items-center gap-1 rounded-md border pl-2 pr-1 py-0.5 text-sm",
|
|
3399
|
+
active ? "bg-accent border-accent-foreground/20" : "hover:bg-muted"
|
|
3400
|
+
),
|
|
3401
|
+
children: [
|
|
3402
|
+
/* @__PURE__ */ jsxs5(
|
|
3403
|
+
"button",
|
|
3404
|
+
{
|
|
3405
|
+
type: "button",
|
|
3406
|
+
role: "group",
|
|
3407
|
+
"aria-selected": active,
|
|
3408
|
+
className: "inline-flex items-center gap-1 py-0.5",
|
|
3409
|
+
onClick: onSelect,
|
|
3410
|
+
onDoubleClick: () => !readOnly && setEditing(true),
|
|
3411
|
+
onKeyDown: (e) => {
|
|
3412
|
+
if (e.key === "F2" && !readOnly) {
|
|
3413
|
+
e.preventDefault();
|
|
3414
|
+
setEditing(true);
|
|
3415
|
+
}
|
|
3416
|
+
},
|
|
3417
|
+
title: readOnly ? group.label : `${group.label} -- F2 to rename`,
|
|
3418
|
+
children: [
|
|
3419
|
+
group.label,
|
|
3420
|
+
/* @__PURE__ */ jsx6("span", { className: "text-xs text-muted-foreground", children: fieldCount })
|
|
3421
|
+
]
|
|
3422
|
+
}
|
|
3423
|
+
),
|
|
3424
|
+
!readOnly && /* @__PURE__ */ jsxs5("span", { className: "flex items-center opacity-0 group-hover/group:opacity-100 focus-within:opacity-100", children: [
|
|
3425
|
+
onMoveLeft && /* @__PURE__ */ jsx6(
|
|
3426
|
+
IconButton2,
|
|
3427
|
+
{
|
|
3428
|
+
label: `Move ${group.label} left`,
|
|
3429
|
+
revealOnHover: true,
|
|
3430
|
+
onClick: onMoveLeft,
|
|
3431
|
+
children: /* @__PURE__ */ jsx6(ChevronLeft, {})
|
|
3432
|
+
}
|
|
3433
|
+
),
|
|
3434
|
+
onMoveRight && /* @__PURE__ */ jsx6(
|
|
3435
|
+
IconButton2,
|
|
3436
|
+
{
|
|
3437
|
+
label: `Move ${group.label} right`,
|
|
3438
|
+
revealOnHover: true,
|
|
3439
|
+
onClick: onMoveRight,
|
|
3440
|
+
children: /* @__PURE__ */ jsx6(ChevronRight2, {})
|
|
3441
|
+
}
|
|
3442
|
+
),
|
|
3443
|
+
/* @__PURE__ */ jsx6(
|
|
3444
|
+
IconButton2,
|
|
3445
|
+
{
|
|
3446
|
+
label: `Remove ${group.label}`,
|
|
3447
|
+
tone: "destructive",
|
|
3448
|
+
revealOnHover: true,
|
|
3449
|
+
onClick: onRemove,
|
|
3450
|
+
children: /* @__PURE__ */ jsx6(Trash22, {})
|
|
3451
|
+
}
|
|
3452
|
+
)
|
|
3453
|
+
] })
|
|
3454
|
+
]
|
|
3455
|
+
}
|
|
3456
|
+
);
|
|
3457
|
+
}
|
|
1567
3458
|
var WIDTH_LABELS = {
|
|
1568
3459
|
full: "Full",
|
|
1569
3460
|
sm: "Small",
|
|
@@ -1584,9 +3475,10 @@ function Chip({
|
|
|
1584
3475
|
onDragEnd,
|
|
1585
3476
|
width,
|
|
1586
3477
|
onWidth,
|
|
1587
|
-
widthDisabled
|
|
3478
|
+
widthDisabled,
|
|
3479
|
+
muted = false
|
|
1588
3480
|
}) {
|
|
1589
|
-
return /* @__PURE__ */
|
|
3481
|
+
return /* @__PURE__ */ jsxs5(
|
|
1590
3482
|
"div",
|
|
1591
3483
|
{
|
|
1592
3484
|
draggable: !readOnly,
|
|
@@ -1596,11 +3488,14 @@ function Chip({
|
|
|
1596
3488
|
onDragStart();
|
|
1597
3489
|
},
|
|
1598
3490
|
onDragEnd,
|
|
1599
|
-
className:
|
|
3491
|
+
className: cn2(
|
|
3492
|
+
"group/chip flex items-center gap-1 rounded-md border bg-background px-2 py-1 text-sm cursor-grab active:cursor-grabbing",
|
|
3493
|
+
muted && "border-dashed text-muted-foreground"
|
|
3494
|
+
),
|
|
1600
3495
|
children: [
|
|
1601
|
-
/* @__PURE__ */
|
|
1602
|
-
/* @__PURE__ */
|
|
1603
|
-
onWidth && /* @__PURE__ */
|
|
3496
|
+
/* @__PURE__ */ jsx6(GripVertical, { className: "h-3.5 w-3.5 text-muted-foreground/60 shrink-0" }),
|
|
3497
|
+
/* @__PURE__ */ jsx6("span", { className: "truncate", children: label }),
|
|
3498
|
+
onWidth && /* @__PURE__ */ jsx6(
|
|
1604
3499
|
"select",
|
|
1605
3500
|
{
|
|
1606
3501
|
value: width ?? "full",
|
|
@@ -1608,10 +3503,10 @@ function Chip({
|
|
|
1608
3503
|
disabled: readOnly || widthDisabled,
|
|
1609
3504
|
title: widthDisabled ? "Overridden by raw CSS columns" : "Column width",
|
|
1610
3505
|
className: "h-6 rounded border bg-background px-1 text-xs text-muted-foreground disabled:opacity-50",
|
|
1611
|
-
children: ["full", "sm", "md", "lg"].map((w) => /* @__PURE__ */
|
|
3506
|
+
children: ["full", "sm", "md", "lg"].map((w) => /* @__PURE__ */ jsx6("option", { value: w, title: WIDTH_LABELS[w], children: WIDTH_SHORT[w] }, w))
|
|
1612
3507
|
}
|
|
1613
3508
|
),
|
|
1614
|
-
onRemove && !readOnly && /* @__PURE__ */
|
|
3509
|
+
onRemove && !readOnly && /* @__PURE__ */ jsx6(IconButton2, { label: "Remove from row", onClick: onRemove, children: /* @__PURE__ */ jsx6(Trash22, {}) })
|
|
1615
3510
|
]
|
|
1616
3511
|
}
|
|
1617
3512
|
);
|
|
@@ -1634,48 +3529,48 @@ function RowCard({
|
|
|
1634
3529
|
onWidth,
|
|
1635
3530
|
onArrange
|
|
1636
3531
|
}) {
|
|
1637
|
-
const [over, setOver] =
|
|
3532
|
+
const [over, setOver] = useState4(false);
|
|
1638
3533
|
const isFlow = row.arrange === "flow";
|
|
1639
|
-
return /* @__PURE__ */
|
|
1640
|
-
/* @__PURE__ */
|
|
1641
|
-
/* @__PURE__ */
|
|
3534
|
+
return /* @__PURE__ */ jsxs5("div", { className: "rounded-lg border p-3", children: [
|
|
3535
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2 mb-2", children: [
|
|
3536
|
+
/* @__PURE__ */ jsxs5("span", { className: "heading-meta", children: [
|
|
1642
3537
|
"Row ",
|
|
1643
3538
|
index + 1
|
|
1644
3539
|
] }),
|
|
1645
|
-
!readOnly && /* @__PURE__ */
|
|
1646
|
-
/* @__PURE__ */
|
|
3540
|
+
!readOnly && /* @__PURE__ */ jsxs5("span", { className: "ml-auto flex gap-0.5", children: [
|
|
3541
|
+
/* @__PURE__ */ jsx6(
|
|
1647
3542
|
IconButton2,
|
|
1648
3543
|
{
|
|
1649
3544
|
label: "Move row up",
|
|
1650
3545
|
disabled: index === 0,
|
|
1651
3546
|
onClick: onMoveUp,
|
|
1652
|
-
children: /* @__PURE__ */
|
|
3547
|
+
children: /* @__PURE__ */ jsx6(ChevronUp2, {})
|
|
1653
3548
|
}
|
|
1654
3549
|
),
|
|
1655
|
-
/* @__PURE__ */
|
|
3550
|
+
/* @__PURE__ */ jsx6(
|
|
1656
3551
|
IconButton2,
|
|
1657
3552
|
{
|
|
1658
3553
|
label: "Move row down",
|
|
1659
3554
|
disabled: index === total - 1,
|
|
1660
3555
|
onClick: onMoveDown,
|
|
1661
|
-
children: /* @__PURE__ */
|
|
3556
|
+
children: /* @__PURE__ */ jsx6(ChevronDown2, {})
|
|
1662
3557
|
}
|
|
1663
3558
|
),
|
|
1664
|
-
/* @__PURE__ */
|
|
3559
|
+
/* @__PURE__ */ jsx6(
|
|
1665
3560
|
IconButton2,
|
|
1666
3561
|
{
|
|
1667
3562
|
label: "Delete row",
|
|
1668
3563
|
tone: "destructive",
|
|
1669
3564
|
onClick: onDelete,
|
|
1670
|
-
children: /* @__PURE__ */
|
|
3565
|
+
children: /* @__PURE__ */ jsx6(Trash22, {})
|
|
1671
3566
|
}
|
|
1672
3567
|
)
|
|
1673
3568
|
] })
|
|
1674
3569
|
] }),
|
|
1675
|
-
/* @__PURE__ */
|
|
1676
|
-
/* @__PURE__ */
|
|
1677
|
-
/* @__PURE__ */
|
|
1678
|
-
|
|
3570
|
+
/* @__PURE__ */ jsxs5("div", { className: "mb-2 flex items-center gap-2", children: [
|
|
3571
|
+
/* @__PURE__ */ jsx6("label", { className: "text-xs text-muted-foreground w-16", children: "Group" }),
|
|
3572
|
+
/* @__PURE__ */ jsx6(
|
|
3573
|
+
Input3,
|
|
1679
3574
|
{
|
|
1680
3575
|
value: row.group ?? "",
|
|
1681
3576
|
onChange: (e) => onGroup(e.target.value),
|
|
@@ -1685,9 +3580,9 @@ function RowCard({
|
|
|
1685
3580
|
}
|
|
1686
3581
|
)
|
|
1687
3582
|
] }),
|
|
1688
|
-
/* @__PURE__ */
|
|
1689
|
-
/* @__PURE__ */
|
|
1690
|
-
/* @__PURE__ */
|
|
3583
|
+
/* @__PURE__ */ jsxs5("div", { className: "mb-2 flex items-center gap-2", children: [
|
|
3584
|
+
/* @__PURE__ */ jsx6("label", { className: "text-xs text-muted-foreground w-16", children: "Arrange" }),
|
|
3585
|
+
/* @__PURE__ */ jsxs5(
|
|
1691
3586
|
"select",
|
|
1692
3587
|
{
|
|
1693
3588
|
value: isFlow ? "flow" : "grid",
|
|
@@ -1696,13 +3591,13 @@ function RowCard({
|
|
|
1696
3591
|
title: "Grid: sized columns. Inline: fields at natural width, wrapping.",
|
|
1697
3592
|
className: "h-7 rounded-md border bg-background px-2 text-sm text-foreground disabled:opacity-50",
|
|
1698
3593
|
children: [
|
|
1699
|
-
/* @__PURE__ */
|
|
1700
|
-
/* @__PURE__ */
|
|
3594
|
+
/* @__PURE__ */ jsx6("option", { value: "grid", children: "Grid (columns)" }),
|
|
3595
|
+
/* @__PURE__ */ jsx6("option", { value: "flow", children: "Inline (natural width)" })
|
|
1701
3596
|
]
|
|
1702
3597
|
}
|
|
1703
3598
|
)
|
|
1704
3599
|
] }),
|
|
1705
|
-
/* @__PURE__ */
|
|
3600
|
+
/* @__PURE__ */ jsx6(
|
|
1706
3601
|
"div",
|
|
1707
3602
|
{
|
|
1708
3603
|
className: `min-h-10 rounded-md border border-dashed p-2 flex flex-wrap gap-1 ${over && dragActive ? "border-primary bg-accent" : "border-border"}`,
|
|
@@ -1717,7 +3612,7 @@ function RowCard({
|
|
|
1717
3612
|
setOver(false);
|
|
1718
3613
|
onDropKey(e.dataTransfer.getData(DRAG_MIME) || null);
|
|
1719
3614
|
},
|
|
1720
|
-
children: row.fields.length === 0 ? /* @__PURE__ */
|
|
3615
|
+
children: row.fields.length === 0 ? /* @__PURE__ */ jsx6("span", { className: "text-xs text-muted-foreground p-1", children: "Drop properties here" }) : row.fields.map((key, cell) => /* @__PURE__ */ jsx6(
|
|
1721
3616
|
Chip,
|
|
1722
3617
|
{
|
|
1723
3618
|
label: key,
|
|
@@ -1733,12 +3628,12 @@ function RowCard({
|
|
|
1733
3628
|
))
|
|
1734
3629
|
}
|
|
1735
3630
|
),
|
|
1736
|
-
!isFlow && /* @__PURE__ */
|
|
1737
|
-
/* @__PURE__ */
|
|
1738
|
-
/* @__PURE__ */
|
|
1739
|
-
/* @__PURE__ */
|
|
1740
|
-
/* @__PURE__ */
|
|
1741
|
-
|
|
3631
|
+
!isFlow && /* @__PURE__ */ jsxs5("details", { className: "mt-2", children: [
|
|
3632
|
+
/* @__PURE__ */ jsx6("summary", { className: "cursor-pointer text-xs text-muted-foreground", children: "Advanced: raw CSS columns" }),
|
|
3633
|
+
/* @__PURE__ */ jsxs5("div", { className: "mt-2 flex items-center gap-2", children: [
|
|
3634
|
+
/* @__PURE__ */ jsx6("label", { className: "text-xs text-muted-foreground w-16", children: "Columns" }),
|
|
3635
|
+
/* @__PURE__ */ jsx6(
|
|
3636
|
+
Input3,
|
|
1742
3637
|
{
|
|
1743
3638
|
value: row.columns ?? "",
|
|
1744
3639
|
onChange: (e) => onColumns(e.target.value),
|
|
@@ -1748,9 +3643,9 @@ function RowCard({
|
|
|
1748
3643
|
}
|
|
1749
3644
|
)
|
|
1750
3645
|
] }),
|
|
1751
|
-
/* @__PURE__ */
|
|
3646
|
+
/* @__PURE__ */ jsxs5("p", { className: "mt-1 pl-[4.5rem] text-xs text-muted-foreground", children: [
|
|
1752
3647
|
"Raw ",
|
|
1753
|
-
/* @__PURE__ */
|
|
3648
|
+
/* @__PURE__ */ jsx6("span", { className: "font-mono", children: "grid-template-columns" }),
|
|
1754
3649
|
". When set, it overrides the per-property widths. Blank = use widths."
|
|
1755
3650
|
] })
|
|
1756
3651
|
] })
|
|
@@ -1758,21 +3653,44 @@ function RowCard({
|
|
|
1758
3653
|
}
|
|
1759
3654
|
|
|
1760
3655
|
// src/preview/CapabilitiesPanel.tsx
|
|
1761
|
-
import {
|
|
3656
|
+
import { useMemo as useMemo6 } from "react";
|
|
3657
|
+
|
|
3658
|
+
// src/preview/SchemaSourcePanel.tsx
|
|
3659
|
+
import { useEffect as useEffect3, useRef as useRef3, useState as useState5 } from "react";
|
|
3660
|
+
import { Check, Copy } from "lucide-react";
|
|
1762
3661
|
import { Button as Button2 } from "@petrarca/sonnet-ui";
|
|
1763
3662
|
import { JsonEditor } from "@petrarca/sonnet-ui/json-editor";
|
|
1764
|
-
import {
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
3663
|
+
import { copyTextToClipboard } from "@petrarca/sonnet-core";
|
|
3664
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
3665
|
+
function SchemaSourcePanel({
|
|
3666
|
+
value,
|
|
3667
|
+
editable,
|
|
3668
|
+
onApply,
|
|
3669
|
+
validate
|
|
1768
3670
|
}) {
|
|
1769
|
-
const [text, setText] =
|
|
1770
|
-
|
|
1771
|
-
);
|
|
1772
|
-
const [
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
3671
|
+
const [text, setText] = useState5(value);
|
|
3672
|
+
const [error, setError] = useState5(null);
|
|
3673
|
+
const [copied, setCopied] = useState5(false);
|
|
3674
|
+
const [dirty, setDirty] = useState5(false);
|
|
3675
|
+
const lastValueRef = useRef3(value);
|
|
3676
|
+
useEffect3(() => {
|
|
3677
|
+
if (value === lastValueRef.current) return;
|
|
3678
|
+
if (!dirty) {
|
|
3679
|
+
lastValueRef.current = value;
|
|
3680
|
+
setText(value);
|
|
3681
|
+
setError(null);
|
|
3682
|
+
}
|
|
3683
|
+
}, [value, dirty]);
|
|
3684
|
+
const handleEditorChange = (next) => {
|
|
3685
|
+
setText(next);
|
|
3686
|
+
setDirty(next !== value);
|
|
3687
|
+
};
|
|
3688
|
+
const handleCopy = () => {
|
|
3689
|
+
void copyTextToClipboard(editable ? text : value).then(() => {
|
|
3690
|
+
setCopied(true);
|
|
3691
|
+
setTimeout(() => setCopied(false), 1500);
|
|
3692
|
+
});
|
|
3693
|
+
};
|
|
1776
3694
|
const handleApply = () => {
|
|
1777
3695
|
let parsed;
|
|
1778
3696
|
try {
|
|
@@ -1781,81 +3699,121 @@ function CapabilitiesPanel({
|
|
|
1781
3699
|
setError("Invalid JSON.");
|
|
1782
3700
|
return;
|
|
1783
3701
|
}
|
|
1784
|
-
const
|
|
1785
|
-
if (
|
|
1786
|
-
setError(
|
|
3702
|
+
const validationError = validate?.(parsed);
|
|
3703
|
+
if (validationError) {
|
|
3704
|
+
setError(validationError);
|
|
1787
3705
|
return;
|
|
1788
3706
|
}
|
|
1789
3707
|
setError(null);
|
|
1790
|
-
|
|
3708
|
+
setDirty(false);
|
|
3709
|
+
onApply(parsed);
|
|
1791
3710
|
};
|
|
1792
|
-
return /* @__PURE__ */
|
|
1793
|
-
/* @__PURE__ */
|
|
1794
|
-
/* @__PURE__ */
|
|
1795
|
-
/* @__PURE__ */
|
|
3711
|
+
return /* @__PURE__ */ jsxs6("div", { className: "absolute inset-0 rounded-md border overflow-hidden", children: [
|
|
3712
|
+
/* @__PURE__ */ jsxs6("div", { className: "absolute right-2 top-2 z-10 flex items-center gap-2", children: [
|
|
3713
|
+
error && /* @__PURE__ */ jsx7("span", { className: "rounded bg-destructive px-2 py-1 text-xs font-medium text-destructive-foreground", children: error }),
|
|
3714
|
+
editable && /* @__PURE__ */ jsx7(
|
|
1796
3715
|
Button2,
|
|
1797
3716
|
{
|
|
1798
3717
|
type: "button",
|
|
1799
3718
|
size: "sm",
|
|
1800
3719
|
onClick: handleApply,
|
|
1801
|
-
|
|
3720
|
+
disabled: !dirty,
|
|
1802
3721
|
children: "Apply"
|
|
1803
3722
|
}
|
|
3723
|
+
),
|
|
3724
|
+
/* @__PURE__ */ jsxs6(
|
|
3725
|
+
Button2,
|
|
3726
|
+
{
|
|
3727
|
+
type: "button",
|
|
3728
|
+
variant: "outline",
|
|
3729
|
+
size: "sm",
|
|
3730
|
+
onClick: handleCopy,
|
|
3731
|
+
className: "gap-1.5",
|
|
3732
|
+
children: [
|
|
3733
|
+
copied ? /* @__PURE__ */ jsx7(Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx7(Copy, { className: "h-3.5 w-3.5" }),
|
|
3734
|
+
copied ? "Copied" : "Copy"
|
|
3735
|
+
]
|
|
3736
|
+
}
|
|
1804
3737
|
)
|
|
1805
3738
|
] }),
|
|
1806
|
-
|
|
1807
|
-
/* @__PURE__ */ jsx5("div", { className: "relative flex-1 min-h-0 rounded-md border overflow-hidden", children: /* @__PURE__ */ jsx5(
|
|
3739
|
+
editable ? /* @__PURE__ */ jsx7(
|
|
1808
3740
|
JsonEditor,
|
|
1809
3741
|
{
|
|
1810
3742
|
value: text,
|
|
1811
3743
|
editablePaths: ["/"],
|
|
1812
3744
|
fillContainer: true,
|
|
1813
|
-
onChange:
|
|
3745
|
+
onChange: handleEditorChange
|
|
3746
|
+
}
|
|
3747
|
+
) : /* @__PURE__ */ jsx7(
|
|
3748
|
+
JsonEditor,
|
|
3749
|
+
{
|
|
3750
|
+
value,
|
|
3751
|
+
editablePaths: [],
|
|
3752
|
+
fillContainer: true
|
|
3753
|
+
},
|
|
3754
|
+
value
|
|
3755
|
+
)
|
|
3756
|
+
] });
|
|
3757
|
+
}
|
|
3758
|
+
|
|
3759
|
+
// src/preview/CapabilitiesPanel.tsx
|
|
3760
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
3761
|
+
function CapabilitiesPanel({
|
|
3762
|
+
capabilities,
|
|
3763
|
+
onApply
|
|
3764
|
+
}) {
|
|
3765
|
+
const value = useMemo6(
|
|
3766
|
+
() => JSON.stringify(capabilities ?? {}, null, 2),
|
|
3767
|
+
[capabilities]
|
|
3768
|
+
);
|
|
3769
|
+
return /* @__PURE__ */ jsx8(
|
|
3770
|
+
SchemaSourcePanel,
|
|
3771
|
+
{
|
|
3772
|
+
value,
|
|
3773
|
+
editable: true,
|
|
3774
|
+
validate: (parsed) => {
|
|
3775
|
+
const result = validateCapabilities(parsed);
|
|
3776
|
+
return result.ok ? null : result.errors.join("; ");
|
|
3777
|
+
},
|
|
3778
|
+
onApply: (parsed) => {
|
|
3779
|
+
const result = validateCapabilities(parsed);
|
|
3780
|
+
if (result.ok) onApply?.(result.value);
|
|
1814
3781
|
}
|
|
1815
|
-
|
|
1816
|
-
|
|
3782
|
+
}
|
|
3783
|
+
);
|
|
1817
3784
|
}
|
|
1818
3785
|
|
|
1819
3786
|
// src/preview/PreviewPane.tsx
|
|
1820
|
-
import { Fragment as
|
|
1821
|
-
function
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
className: "absolute right-2 top-2 z-10 gap-1.5",
|
|
1838
|
-
children: [
|
|
1839
|
-
copied ? /* @__PURE__ */ jsx6(Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx6(Copy, { className: "h-3.5 w-3.5" }),
|
|
1840
|
-
copied ? "Copied" : "Copy"
|
|
1841
|
-
]
|
|
1842
|
-
}
|
|
1843
|
-
),
|
|
1844
|
-
/* @__PURE__ */ jsx6(JsonEditor2, { value, editablePaths: [], fillContainer: true }, value)
|
|
1845
|
-
] });
|
|
3787
|
+
import { Fragment as Fragment4, jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
3788
|
+
function validateRootSchema(parsed) {
|
|
3789
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
3790
|
+
return "Schema must be a JSON object.";
|
|
3791
|
+
}
|
|
3792
|
+
const s = parsed;
|
|
3793
|
+
if (s.type !== "object") return 'Root schema must have type: "object".';
|
|
3794
|
+
if (typeof s.properties !== "object" || s.properties === null) {
|
|
3795
|
+
return "Root schema must have a properties object.";
|
|
3796
|
+
}
|
|
3797
|
+
return null;
|
|
3798
|
+
}
|
|
3799
|
+
function validateUiSchema(parsed) {
|
|
3800
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
3801
|
+
return "UI Schema must be a JSON object.";
|
|
3802
|
+
}
|
|
3803
|
+
return null;
|
|
1846
3804
|
}
|
|
1847
3805
|
function PreviewTabList({ flags }) {
|
|
1848
|
-
return /* @__PURE__ */
|
|
1849
|
-
flags.showForm && /* @__PURE__ */
|
|
1850
|
-
flags.isFull && flags.showForm && /* @__PURE__ */
|
|
1851
|
-
flags.showTable && /* @__PURE__ */
|
|
1852
|
-
/* @__PURE__ */
|
|
1853
|
-
flags.showJson && /* @__PURE__ */
|
|
1854
|
-
flags.isFull && /* @__PURE__ */
|
|
1855
|
-
flags.isFull && /* @__PURE__ */
|
|
1856
|
-
flags.showCapabilities && /* @__PURE__ */
|
|
1857
|
-
/* @__PURE__ */
|
|
1858
|
-
/* @__PURE__ */
|
|
3806
|
+
return /* @__PURE__ */ jsxs7(TabsList, { className: "mb-3", children: [
|
|
3807
|
+
flags.showForm && /* @__PURE__ */ jsx9(TabsTrigger, { value: "form", children: "Form" }),
|
|
3808
|
+
flags.isFull && flags.showForm && /* @__PURE__ */ jsx9(TabsTrigger, { value: "layout", children: "Form Layout" }),
|
|
3809
|
+
flags.showTable && /* @__PURE__ */ jsx9(TabsTrigger, { value: "table", children: "Table" }),
|
|
3810
|
+
/* @__PURE__ */ jsx9(TabsSeparator, {}),
|
|
3811
|
+
flags.showJson && /* @__PURE__ */ jsx9(TabsTrigger, { value: "schema", children: "JSON Schema" }),
|
|
3812
|
+
flags.isFull && /* @__PURE__ */ jsx9(TabsTrigger, { value: "ui", children: "UI Schema" }),
|
|
3813
|
+
flags.isFull && /* @__PURE__ */ jsx9(TabsTrigger, { value: "combined", children: "Combined" }),
|
|
3814
|
+
flags.showCapabilities && /* @__PURE__ */ jsxs7(Fragment4, { children: [
|
|
3815
|
+
/* @__PURE__ */ jsx9(TabsSeparator, {}),
|
|
3816
|
+
/* @__PURE__ */ jsx9(TabsTrigger, { value: "capabilities", children: "Capabilities" })
|
|
1859
3817
|
] })
|
|
1860
3818
|
] });
|
|
1861
3819
|
}
|
|
@@ -1873,22 +3831,22 @@ function PreviewPane({
|
|
|
1873
3831
|
showJson: caps.previews.includes("json"),
|
|
1874
3832
|
showCapabilities: caps.showCapabilities
|
|
1875
3833
|
};
|
|
1876
|
-
const [data, setData] =
|
|
3834
|
+
const [data, setData] = useState6(
|
|
1877
3835
|
() => extractSchemaDefaults(schema)
|
|
1878
3836
|
);
|
|
1879
|
-
const schemaText =
|
|
1880
|
-
const uiSchemaText =
|
|
3837
|
+
const schemaText = useMemo7(() => JSON.stringify(schema, null, 2), [schema]);
|
|
3838
|
+
const uiSchemaText = useMemo7(
|
|
1881
3839
|
() => JSON.stringify(uiSchema ?? {}, null, 2),
|
|
1882
3840
|
[uiSchema]
|
|
1883
3841
|
);
|
|
1884
|
-
const embeddedText =
|
|
3842
|
+
const embeddedText = useMemo7(
|
|
1885
3843
|
() => JSON.stringify(embedUiSchema(schema, uiSchema ?? {}), null, 2),
|
|
1886
3844
|
[schema, uiSchema]
|
|
1887
3845
|
);
|
|
1888
3846
|
const defaultTab = flags.showForm ? "form" : flags.showTable ? "table" : "schema";
|
|
1889
|
-
return /* @__PURE__ */
|
|
1890
|
-
/* @__PURE__ */
|
|
1891
|
-
flags.showForm && /* @__PURE__ */
|
|
3847
|
+
return /* @__PURE__ */ jsxs7(Tabs, { defaultValue: defaultTab, className: "flex flex-col h-full min-h-0", children: [
|
|
3848
|
+
/* @__PURE__ */ jsx9(PreviewTabList, { flags }),
|
|
3849
|
+
flags.showForm && /* @__PURE__ */ jsx9(TabsContent, { value: "form", className: "flex-1 min-h-0 overflow-auto", children: /* @__PURE__ */ jsx9(
|
|
1892
3850
|
JsonSchemaFormRenderer3,
|
|
1893
3851
|
{
|
|
1894
3852
|
schema,
|
|
@@ -1899,11 +3857,12 @@ function PreviewPane({
|
|
|
1899
3857
|
showExtraProperties: false
|
|
1900
3858
|
}
|
|
1901
3859
|
) }),
|
|
1902
|
-
flags.isFull && flags.showForm && /* @__PURE__ */
|
|
1903
|
-
flags.showTable && /* @__PURE__ */
|
|
1904
|
-
/* @__PURE__ */
|
|
3860
|
+
flags.isFull && flags.showForm && /* @__PURE__ */ jsx9(TabsContent, { value: "layout", className: "flex-1 min-h-0 overflow-hidden", children: /* @__PURE__ */ jsx9(FormLayoutDesigner, { state }) }),
|
|
3861
|
+
flags.showTable && /* @__PURE__ */ jsx9(TabsContent, { value: "table", className: "flex-1 min-h-0 overflow-auto", children: /* @__PURE__ */ jsx9(TablePreview, { schema }) }),
|
|
3862
|
+
/* @__PURE__ */ jsx9(
|
|
1905
3863
|
SourceTabPanels,
|
|
1906
3864
|
{
|
|
3865
|
+
state,
|
|
1907
3866
|
flags,
|
|
1908
3867
|
schemaText,
|
|
1909
3868
|
uiSchemaText,
|
|
@@ -1915,6 +3874,7 @@ function PreviewPane({
|
|
|
1915
3874
|
] });
|
|
1916
3875
|
}
|
|
1917
3876
|
function SourceTabPanels({
|
|
3877
|
+
state,
|
|
1918
3878
|
flags,
|
|
1919
3879
|
schemaText,
|
|
1920
3880
|
uiSchemaText,
|
|
@@ -1922,11 +3882,36 @@ function SourceTabPanels({
|
|
|
1922
3882
|
capabilities,
|
|
1923
3883
|
onCapabilitiesChange
|
|
1924
3884
|
}) {
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
flags.
|
|
1928
|
-
|
|
1929
|
-
|
|
3885
|
+
const canEditSource = !state.capabilities.locked && !state.capabilities.scoped;
|
|
3886
|
+
return /* @__PURE__ */ jsxs7(Fragment4, { children: [
|
|
3887
|
+
flags.showJson && /* @__PURE__ */ jsx9(TabsContent, { value: "schema", className: "flex-1 min-h-0 relative", children: /* @__PURE__ */ jsx9(
|
|
3888
|
+
SchemaSourcePanel,
|
|
3889
|
+
{
|
|
3890
|
+
value: schemaText,
|
|
3891
|
+
editable: canEditSource,
|
|
3892
|
+
validate: validateRootSchema,
|
|
3893
|
+
onApply: (parsed) => state.replaceSchema(parsed)
|
|
3894
|
+
}
|
|
3895
|
+
) }),
|
|
3896
|
+
flags.isFull && /* @__PURE__ */ jsx9(TabsContent, { value: "ui", className: "flex-1 min-h-0 relative", children: /* @__PURE__ */ jsx9(
|
|
3897
|
+
SchemaSourcePanel,
|
|
3898
|
+
{
|
|
3899
|
+
value: uiSchemaText,
|
|
3900
|
+
editable: canEditSource,
|
|
3901
|
+
validate: validateUiSchema,
|
|
3902
|
+
onApply: (parsed) => state.replaceUiSchema(parsed)
|
|
3903
|
+
}
|
|
3904
|
+
) }),
|
|
3905
|
+
flags.isFull && /* @__PURE__ */ jsx9(TabsContent, { value: "combined", className: "flex-1 min-h-0 relative", children: /* @__PURE__ */ jsx9(
|
|
3906
|
+
SchemaSourcePanel,
|
|
3907
|
+
{
|
|
3908
|
+
value: embeddedText,
|
|
3909
|
+
editable: false,
|
|
3910
|
+
onApply: () => {
|
|
3911
|
+
}
|
|
3912
|
+
}
|
|
3913
|
+
) }),
|
|
3914
|
+
flags.showCapabilities && /* @__PURE__ */ jsx9(TabsContent, { value: "capabilities", className: "flex-1 min-h-0 relative", children: /* @__PURE__ */ jsx9(
|
|
1930
3915
|
CapabilitiesPanel,
|
|
1931
3916
|
{
|
|
1932
3917
|
capabilities,
|
|
@@ -1937,20 +3922,27 @@ function SourceTabPanels({
|
|
|
1937
3922
|
}
|
|
1938
3923
|
|
|
1939
3924
|
// src/inspector/Inspector.tsx
|
|
1940
|
-
import { useEffect as
|
|
3925
|
+
import { useEffect as useEffect5, useMemo as useMemo14, useRef as useRef6, useState as useState8 } from "react";
|
|
1941
3926
|
import {
|
|
1942
3927
|
Tabs as Tabs2,
|
|
1943
3928
|
TabsList as TabsList2,
|
|
1944
3929
|
TabsTrigger as TabsTrigger2,
|
|
1945
3930
|
TabsContent as TabsContent2,
|
|
1946
|
-
Input as
|
|
3931
|
+
Input as Input5,
|
|
1947
3932
|
IconButton as IconButton4
|
|
1948
3933
|
} from "@petrarca/sonnet-ui";
|
|
1949
3934
|
import { Pencil, Check as Check2, X } from "lucide-react";
|
|
1950
3935
|
|
|
1951
3936
|
// src/inspector/DataTab.tsx
|
|
1952
|
-
import { useMemo as
|
|
3937
|
+
import { useMemo as useMemo8, useRef as useRef4 } from "react";
|
|
1953
3938
|
import { JsonSchemaFormRenderer as JsonSchemaFormRenderer4 } from "@petrarca/sonnet-forms";
|
|
3939
|
+
import {
|
|
3940
|
+
Select,
|
|
3941
|
+
SelectTrigger,
|
|
3942
|
+
SelectValue,
|
|
3943
|
+
SelectContent,
|
|
3944
|
+
SelectItem
|
|
3945
|
+
} from "@petrarca/sonnet-ui";
|
|
1954
3946
|
|
|
1955
3947
|
// src/inspector/metaSchemas.ts
|
|
1956
3948
|
var PROPERTY_TYPES = [
|
|
@@ -1959,8 +3951,20 @@ var PROPERTY_TYPES = [
|
|
|
1959
3951
|
"integer",
|
|
1960
3952
|
"boolean",
|
|
1961
3953
|
"object",
|
|
1962
|
-
"array"
|
|
3954
|
+
"array",
|
|
3955
|
+
"null"
|
|
1963
3956
|
];
|
|
3957
|
+
var PROPERTY_TYPE_CHOICES = [...PROPERTY_TYPES, "oneOf"];
|
|
3958
|
+
var PROPERTY_TYPE_NAMES = {
|
|
3959
|
+
string: "String",
|
|
3960
|
+
number: "Number",
|
|
3961
|
+
integer: "Integer",
|
|
3962
|
+
boolean: "Boolean",
|
|
3963
|
+
object: "Object",
|
|
3964
|
+
array: "Array",
|
|
3965
|
+
null: "Display (text only)",
|
|
3966
|
+
oneOf: "One of (variant)"
|
|
3967
|
+
};
|
|
1964
3968
|
var STRING_FORMATS = [
|
|
1965
3969
|
"",
|
|
1966
3970
|
"email",
|
|
@@ -1995,37 +3999,100 @@ function validationProps(type) {
|
|
|
1995
3999
|
return {};
|
|
1996
4000
|
}
|
|
1997
4001
|
}
|
|
1998
|
-
|
|
4002
|
+
var BRANCH_META_TYPE = "branch";
|
|
4003
|
+
function typeTraits(type) {
|
|
4004
|
+
const isDisplay = type === "null";
|
|
4005
|
+
const isOneOf3 = type === "oneOf";
|
|
1999
4006
|
const isContainer2 = type === "object" || type === "array";
|
|
4007
|
+
return {
|
|
4008
|
+
isDisplay,
|
|
4009
|
+
isOneOf: isOneOf3,
|
|
4010
|
+
// Containers, display (null), and oneOf have no meaningful default value.
|
|
4011
|
+
noDefault: isContainer2 || isDisplay || isOneOf3,
|
|
4012
|
+
// Display notes and oneOf unions are never "required" on their own.
|
|
4013
|
+
noRequired: isDisplay || isOneOf3,
|
|
4014
|
+
// A fixed value (const) is only meaningful on scalar properties.
|
|
4015
|
+
isScalar: type === "string" || type === "number" || type === "integer" || type === "boolean"
|
|
4016
|
+
};
|
|
4017
|
+
}
|
|
4018
|
+
function buildReferenceMetaSchema() {
|
|
4019
|
+
return {
|
|
4020
|
+
title: "",
|
|
4021
|
+
type: "object",
|
|
4022
|
+
properties: {
|
|
4023
|
+
title: { type: "string", title: "Label (title)" },
|
|
4024
|
+
description: {
|
|
4025
|
+
type: "string",
|
|
4026
|
+
title: "Description",
|
|
4027
|
+
"x-ui-widget": "textarea"
|
|
4028
|
+
},
|
|
4029
|
+
required: { type: "boolean", title: "Required" },
|
|
4030
|
+
default: { type: "string", title: "Default" }
|
|
4031
|
+
}
|
|
4032
|
+
};
|
|
4033
|
+
}
|
|
4034
|
+
function buildDataMetaSchema(type) {
|
|
4035
|
+
if (type === BRANCH_META_TYPE) {
|
|
4036
|
+
return {
|
|
4037
|
+
title: "",
|
|
4038
|
+
type: "object",
|
|
4039
|
+
properties: {
|
|
4040
|
+
title: { type: "string", title: "Branch label (title)" }
|
|
4041
|
+
}
|
|
4042
|
+
};
|
|
4043
|
+
}
|
|
4044
|
+
const { isDisplay, noDefault, noRequired, isScalar, isOneOf: isOneOf3 } = typeTraits(type);
|
|
2000
4045
|
const properties = {
|
|
2001
|
-
type: {
|
|
2002
|
-
|
|
4046
|
+
type: {
|
|
4047
|
+
type: "string",
|
|
4048
|
+
title: "Type",
|
|
4049
|
+
enum: [...PROPERTY_TYPE_CHOICES],
|
|
4050
|
+
"x-ui-options": { enumNames: PROPERTY_TYPE_NAMES }
|
|
4051
|
+
},
|
|
4052
|
+
title: { type: "string", title: isDisplay ? "Text" : "Label (title)" },
|
|
2003
4053
|
description: {
|
|
2004
4054
|
type: "string",
|
|
2005
4055
|
title: "Description",
|
|
2006
4056
|
"x-ui-widget": "textarea"
|
|
2007
4057
|
},
|
|
2008
|
-
|
|
2009
|
-
|
|
4058
|
+
...isOneOf3 ? {
|
|
4059
|
+
discriminator: {
|
|
4060
|
+
type: "string",
|
|
4061
|
+
title: "Discriminator property",
|
|
4062
|
+
"x-ui-description": "Shared property whose const value tags each branch. Auto-detected when blank."
|
|
4063
|
+
}
|
|
4064
|
+
} : {},
|
|
4065
|
+
...noRequired ? {} : { required: { type: "boolean", title: "Required" } },
|
|
4066
|
+
...noDefault ? {} : { default: { type: "string", title: "Default" } },
|
|
4067
|
+
...isScalar ? {
|
|
4068
|
+
const: {
|
|
4069
|
+
type: "string",
|
|
4070
|
+
title: "Const (fixed value)",
|
|
4071
|
+
"x-ui-description": "Fixed value this property must equal. Leave blank for none."
|
|
4072
|
+
}
|
|
4073
|
+
} : {},
|
|
2010
4074
|
...validationProps(type)
|
|
2011
4075
|
};
|
|
2012
4076
|
return { title: "", type: "object", properties };
|
|
2013
4077
|
}
|
|
2014
|
-
function propertyToDataValues(
|
|
4078
|
+
function propertyToDataValues(prop2, required) {
|
|
2015
4079
|
return {
|
|
2016
|
-
type:
|
|
2017
|
-
title:
|
|
2018
|
-
description:
|
|
2019
|
-
default:
|
|
4080
|
+
type: prop2.oneOf ? "oneOf" : prop2.type ?? "string",
|
|
4081
|
+
title: prop2.title,
|
|
4082
|
+
description: prop2.description,
|
|
4083
|
+
default: prop2.default,
|
|
4084
|
+
// const may be any JSON type; the Data-tab field edits it as a string.
|
|
4085
|
+
const: prop2.const == null ? void 0 : String(prop2.const),
|
|
2020
4086
|
required,
|
|
2021
|
-
minLength:
|
|
2022
|
-
maxLength:
|
|
2023
|
-
pattern:
|
|
2024
|
-
format:
|
|
2025
|
-
minimum:
|
|
2026
|
-
maximum:
|
|
2027
|
-
minItems:
|
|
2028
|
-
maxItems:
|
|
4087
|
+
minLength: prop2.minLength,
|
|
4088
|
+
maxLength: prop2.maxLength,
|
|
4089
|
+
pattern: prop2.pattern,
|
|
4090
|
+
format: prop2.format,
|
|
4091
|
+
minimum: prop2.minimum,
|
|
4092
|
+
maximum: prop2.maximum,
|
|
4093
|
+
minItems: prop2.minItems,
|
|
4094
|
+
maxItems: prop2.maxItems,
|
|
4095
|
+
discriminator: prop2.discriminator?.propertyName
|
|
2029
4096
|
};
|
|
2030
4097
|
}
|
|
2031
4098
|
function dataValuesToPatch(values) {
|
|
@@ -2043,6 +4110,7 @@ function dataValuesToPatch(values) {
|
|
|
2043
4110
|
setStr("title", values.title);
|
|
2044
4111
|
setStr("description", values.description);
|
|
2045
4112
|
setStr("default", values.default);
|
|
4113
|
+
setStr("const", values.const);
|
|
2046
4114
|
setStr("format", values.format);
|
|
2047
4115
|
setStr("pattern", values.pattern);
|
|
2048
4116
|
setNum("minLength", values.minLength);
|
|
@@ -2051,28 +4119,31 @@ function dataValuesToPatch(values) {
|
|
|
2051
4119
|
setNum("maximum", values.maximum);
|
|
2052
4120
|
setNum("minItems", values.minItems);
|
|
2053
4121
|
setNum("maxItems", values.maxItems);
|
|
4122
|
+
if (typeof values.discriminator === "string" && values.discriminator.length) {
|
|
4123
|
+
patch.discriminator = { propertyName: values.discriminator };
|
|
4124
|
+
} else {
|
|
4125
|
+
patch.discriminator = void 0;
|
|
4126
|
+
}
|
|
2054
4127
|
return patch;
|
|
2055
4128
|
}
|
|
2056
4129
|
|
|
2057
4130
|
// src/inspector/DataTab.tsx
|
|
2058
|
-
import { jsx as
|
|
4131
|
+
import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2059
4132
|
function DataTab({ node, state }) {
|
|
2060
|
-
const
|
|
2061
|
-
const
|
|
2062
|
-
const
|
|
4133
|
+
const isBranch = node.slotKind === "branch";
|
|
4134
|
+
const type = isBranch ? BRANCH_META_TYPE : node.property.oneOf ? "oneOf" : node.property.type ?? "string";
|
|
4135
|
+
const schema = useMemo8(() => buildDataMetaSchema(type), [type]);
|
|
4136
|
+
const values = useMemo8(
|
|
2063
4137
|
() => propertyToDataValues(node.property, node.required),
|
|
2064
4138
|
[node.property, node.required]
|
|
2065
4139
|
);
|
|
2066
|
-
const prevRef =
|
|
4140
|
+
const prevRef = useRef4(values);
|
|
2067
4141
|
prevRef.current = values;
|
|
2068
4142
|
const handleChange = (data) => {
|
|
2069
4143
|
const next = data;
|
|
2070
4144
|
const prev = prevRef.current;
|
|
2071
|
-
if (next.type !== prev.type) {
|
|
2072
|
-
state.changePropertyType(
|
|
2073
|
-
node.path,
|
|
2074
|
-
next.type
|
|
2075
|
-
);
|
|
4145
|
+
if (!isBranch && next.type !== prev.type) {
|
|
4146
|
+
state.changePropertyType(node.path, next.type);
|
|
2076
4147
|
return;
|
|
2077
4148
|
}
|
|
2078
4149
|
if (next.required !== prev.required) {
|
|
@@ -2081,30 +4152,66 @@ function DataTab({ node, state }) {
|
|
|
2081
4152
|
}
|
|
2082
4153
|
state.updateProperty(node.path, dataValuesToPatch(next));
|
|
2083
4154
|
};
|
|
2084
|
-
return /* @__PURE__ */
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
4155
|
+
return /* @__PURE__ */ jsxs8("div", { className: "space-y-3", children: [
|
|
4156
|
+
!isBranch && /* @__PURE__ */ jsx10(ReferencePicker, { node, state, disabled: state.readOnly }),
|
|
4157
|
+
/* @__PURE__ */ jsx10(
|
|
4158
|
+
JsonSchemaFormRenderer4,
|
|
4159
|
+
{
|
|
4160
|
+
schema,
|
|
4161
|
+
data: values,
|
|
4162
|
+
onChange: handleChange,
|
|
4163
|
+
deferStateUpdates: true,
|
|
4164
|
+
showActions: false,
|
|
4165
|
+
showExtraProperties: false,
|
|
4166
|
+
readOnly: state.readOnly
|
|
4167
|
+
},
|
|
4168
|
+
pathKey(node.path) + ":" + type
|
|
4169
|
+
)
|
|
4170
|
+
] });
|
|
4171
|
+
}
|
|
4172
|
+
function availableDefinitions(state, node) {
|
|
4173
|
+
const defs = state.schema.$defs ?? state.schema.definitions;
|
|
4174
|
+
if (!defs) return [];
|
|
4175
|
+
const selfName = node.path.length === 1 && node.path[0].kind === "def" ? node.path[0].name : void 0;
|
|
4176
|
+
return Object.keys(defs).filter((name) => name !== selfName);
|
|
4177
|
+
}
|
|
4178
|
+
function ReferencePicker({
|
|
4179
|
+
node,
|
|
4180
|
+
state,
|
|
4181
|
+
disabled
|
|
4182
|
+
}) {
|
|
4183
|
+
const names = availableDefinitions(state, node);
|
|
4184
|
+
if (names.length === 0) return null;
|
|
4185
|
+
const isArray = node.property.type === "array";
|
|
4186
|
+
const label = isArray ? "Reference a definition (array items)" : "Reference a definition";
|
|
4187
|
+
const placeholder = isArray ? "Make items reuse a shared definition..." : "Reuse a shared definition...";
|
|
4188
|
+
return /* @__PURE__ */ jsxs8("div", { className: "space-y-1", children: [
|
|
4189
|
+
/* @__PURE__ */ jsx10("label", { className: "text-xs font-medium text-muted-foreground", children: label }),
|
|
4190
|
+
/* @__PURE__ */ jsxs8(
|
|
4191
|
+
Select,
|
|
4192
|
+
{
|
|
4193
|
+
value: "",
|
|
4194
|
+
onValueChange: (name) => state.setReference(node.path, name),
|
|
4195
|
+
disabled,
|
|
4196
|
+
children: [
|
|
4197
|
+
/* @__PURE__ */ jsx10(SelectTrigger, { className: "h-8 text-sm", children: /* @__PURE__ */ jsx10(SelectValue, { placeholder }) }),
|
|
4198
|
+
/* @__PURE__ */ jsx10(SelectContent, { children: names.map((name) => /* @__PURE__ */ jsx10(SelectItem, { value: name, children: name }, name)) })
|
|
4199
|
+
]
|
|
4200
|
+
}
|
|
4201
|
+
)
|
|
4202
|
+
] });
|
|
2096
4203
|
}
|
|
2097
4204
|
|
|
2098
4205
|
// src/inspector/UITab.tsx
|
|
2099
|
-
import { useMemo as
|
|
4206
|
+
import { useMemo as useMemo9 } from "react";
|
|
2100
4207
|
import { JsonSchemaFormRenderer as JsonSchemaFormRenderer5 } from "@petrarca/sonnet-forms";
|
|
2101
|
-
import { JsonEditor as
|
|
4208
|
+
import { JsonEditor as JsonEditor2 } from "@petrarca/sonnet-ui/json-editor";
|
|
2102
4209
|
import {
|
|
2103
4210
|
WIDGET_OPTION_DEFS,
|
|
2104
4211
|
optionDefsToSchema
|
|
2105
4212
|
} from "@petrarca/sonnet-forms/widget-options";
|
|
2106
|
-
import { FormSelect as
|
|
2107
|
-
import { jsx as
|
|
4213
|
+
import { FormSelect as FormSelect4 } from "@petrarca/sonnet-forms";
|
|
4214
|
+
import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2108
4215
|
var PRESENTATION_META_SCHEMA = {
|
|
2109
4216
|
title: "",
|
|
2110
4217
|
type: "object",
|
|
@@ -2167,14 +4274,15 @@ function PresentationEditor({
|
|
|
2167
4274
|
}
|
|
2168
4275
|
onChange(next);
|
|
2169
4276
|
};
|
|
2170
|
-
return /* @__PURE__ */
|
|
2171
|
-
/* @__PURE__ */
|
|
2172
|
-
/* @__PURE__ */
|
|
4277
|
+
return /* @__PURE__ */ jsxs9("div", { children: [
|
|
4278
|
+
/* @__PURE__ */ jsx11("p", { className: "heading-meta mb-2", children: "Presentation (form overrides)" }),
|
|
4279
|
+
/* @__PURE__ */ jsx11(
|
|
2173
4280
|
JsonSchemaFormRenderer5,
|
|
2174
4281
|
{
|
|
2175
4282
|
schema: PRESENTATION_META_SCHEMA,
|
|
2176
4283
|
data: values,
|
|
2177
4284
|
onChange: handleChange,
|
|
4285
|
+
deferStateUpdates: true,
|
|
2178
4286
|
showActions: false,
|
|
2179
4287
|
showExtraProperties: false,
|
|
2180
4288
|
readOnly
|
|
@@ -2191,14 +4299,15 @@ function OptionsEditor({
|
|
|
2191
4299
|
onChange
|
|
2192
4300
|
}) {
|
|
2193
4301
|
if (optionsSchema) {
|
|
2194
|
-
return /* @__PURE__ */
|
|
2195
|
-
/* @__PURE__ */
|
|
2196
|
-
/* @__PURE__ */
|
|
4302
|
+
return /* @__PURE__ */ jsxs9("div", { children: [
|
|
4303
|
+
/* @__PURE__ */ jsx11("p", { className: "heading-meta mb-2", children: "Options" }),
|
|
4304
|
+
/* @__PURE__ */ jsx11(
|
|
2197
4305
|
JsonSchemaFormRenderer5,
|
|
2198
4306
|
{
|
|
2199
4307
|
schema: optionsSchema,
|
|
2200
4308
|
data: currentOptions,
|
|
2201
4309
|
onChange,
|
|
4310
|
+
deferStateUpdates: true,
|
|
2202
4311
|
showActions: false,
|
|
2203
4312
|
showExtraProperties: false,
|
|
2204
4313
|
readOnly
|
|
@@ -2207,10 +4316,10 @@ function OptionsEditor({
|
|
|
2207
4316
|
)
|
|
2208
4317
|
] });
|
|
2209
4318
|
}
|
|
2210
|
-
return /* @__PURE__ */
|
|
2211
|
-
/* @__PURE__ */
|
|
2212
|
-
/* @__PURE__ */
|
|
2213
|
-
|
|
4319
|
+
return /* @__PURE__ */ jsxs9("div", { children: [
|
|
4320
|
+
/* @__PURE__ */ jsx11("p", { className: "heading-meta mb-2", children: "Options (raw JSON)" }),
|
|
4321
|
+
/* @__PURE__ */ jsx11(
|
|
4322
|
+
JsonEditor2,
|
|
2214
4323
|
{
|
|
2215
4324
|
value: JSON.stringify(currentOptions ?? {}, null, 2),
|
|
2216
4325
|
editablePaths: readOnly ? [] : ["/"],
|
|
@@ -2231,17 +4340,17 @@ function UITab({
|
|
|
2231
4340
|
state,
|
|
2232
4341
|
widgetOptions
|
|
2233
4342
|
}) {
|
|
2234
|
-
const
|
|
4343
|
+
const idBase = pathKey(node.path);
|
|
2235
4344
|
const uiEntry = getUiEntryAtPath(state.uiSchema, node.path) ?? {};
|
|
2236
4345
|
const currentWidget = typeof uiEntry["x-ui-widget"] === "string" ? uiEntry["x-ui-widget"] : null;
|
|
2237
4346
|
const currentOptions = uiEntry["x-ui-options"] ?? {};
|
|
2238
4347
|
const rootLayout = state.uiSchema["x-ui-layout"];
|
|
2239
4348
|
const inheritedLabelPlacement = rootLayout?.labelPlacement ?? "top";
|
|
2240
|
-
const widgetChoices =
|
|
4349
|
+
const widgetChoices = useMemo9(
|
|
2241
4350
|
() => compatibleWidgets(node.property),
|
|
2242
4351
|
[node.property]
|
|
2243
4352
|
);
|
|
2244
|
-
const optionsSchema =
|
|
4353
|
+
const optionsSchema = useMemo9(() => {
|
|
2245
4354
|
if (!currentWidget) return null;
|
|
2246
4355
|
const merged = { ...WIDGET_OPTION_DEFS, ...widgetOptions ?? {} };
|
|
2247
4356
|
const descriptors = merged[currentWidget];
|
|
@@ -2274,11 +4383,11 @@ function UITab({
|
|
|
2274
4383
|
else delete next["x-ui-options"];
|
|
2275
4384
|
state.setUiEntry(node.path, next);
|
|
2276
4385
|
};
|
|
2277
|
-
return /* @__PURE__ */
|
|
2278
|
-
/* @__PURE__ */
|
|
2279
|
-
|
|
4386
|
+
return /* @__PURE__ */ jsxs9("div", { className: "space-y-4", children: [
|
|
4387
|
+
/* @__PURE__ */ jsx11(
|
|
4388
|
+
FormSelect4,
|
|
2280
4389
|
{
|
|
2281
|
-
id: `${
|
|
4390
|
+
id: `${idBase}-widget`,
|
|
2282
4391
|
name: "widget",
|
|
2283
4392
|
label: "Widget",
|
|
2284
4393
|
value: currentWidget ?? "__auto__",
|
|
@@ -2291,29 +4400,29 @@ function UITab({
|
|
|
2291
4400
|
clearable: false
|
|
2292
4401
|
}
|
|
2293
4402
|
),
|
|
2294
|
-
currentWidget && /* @__PURE__ */
|
|
4403
|
+
currentWidget && /* @__PURE__ */ jsx11(
|
|
2295
4404
|
OptionsEditor,
|
|
2296
4405
|
{
|
|
2297
|
-
widget: `${
|
|
4406
|
+
widget: `${idBase}:${currentWidget}`,
|
|
2298
4407
|
optionsSchema,
|
|
2299
4408
|
currentOptions,
|
|
2300
4409
|
readOnly: state.readOnly,
|
|
2301
4410
|
onChange: setOptions
|
|
2302
4411
|
}
|
|
2303
4412
|
),
|
|
2304
|
-
/* @__PURE__ */
|
|
4413
|
+
/* @__PURE__ */ jsx11(
|
|
2305
4414
|
PresentationEditor,
|
|
2306
4415
|
{
|
|
2307
|
-
fieldName:
|
|
4416
|
+
fieldName: idBase,
|
|
2308
4417
|
uiEntry,
|
|
2309
4418
|
readOnly: state.readOnly,
|
|
2310
4419
|
onChange: (entry) => state.setUiEntry(node.path, entry)
|
|
2311
4420
|
}
|
|
2312
4421
|
),
|
|
2313
|
-
/* @__PURE__ */
|
|
2314
|
-
|
|
4422
|
+
/* @__PURE__ */ jsx11(
|
|
4423
|
+
FormSelect4,
|
|
2315
4424
|
{
|
|
2316
|
-
id: `${
|
|
4425
|
+
id: `${idBase}-labelPlacement`,
|
|
2317
4426
|
name: "labelPlacement",
|
|
2318
4427
|
label: "Label placement",
|
|
2319
4428
|
value: currentOptions.labelPlacement ?? "__inherit__",
|
|
@@ -2334,25 +4443,25 @@ function UITab({
|
|
|
2334
4443
|
}
|
|
2335
4444
|
|
|
2336
4445
|
// src/inspector/EnumEditor.tsx
|
|
2337
|
-
import { useEffect as
|
|
2338
|
-
import { Input as
|
|
4446
|
+
import { useEffect as useEffect4, useState as useState7 } from "react";
|
|
4447
|
+
import { Input as Input4, IconButton as IconButton3, Button as Button3 } from "@petrarca/sonnet-ui";
|
|
2339
4448
|
import { Plus as Plus3, Trash2 as Trash23 } from "lucide-react";
|
|
2340
|
-
import { jsx as
|
|
4449
|
+
import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
2341
4450
|
function EnumEditor({
|
|
2342
4451
|
node,
|
|
2343
4452
|
state
|
|
2344
4453
|
}) {
|
|
2345
|
-
const [rows, setRows] =
|
|
4454
|
+
const [rows, setRows] = useState7(
|
|
2346
4455
|
() => readEnumChoices(node.property, getUiEntryAtPath(state.uiSchema, node.path))
|
|
2347
4456
|
);
|
|
2348
|
-
|
|
4457
|
+
useEffect4(() => {
|
|
2349
4458
|
setRows(
|
|
2350
4459
|
readEnumChoices(
|
|
2351
4460
|
node.property,
|
|
2352
4461
|
getUiEntryAtPath(state.uiSchema, node.path)
|
|
2353
4462
|
)
|
|
2354
4463
|
);
|
|
2355
|
-
}, [node.path
|
|
4464
|
+
}, [pathKey(node.path)]);
|
|
2356
4465
|
const canEditLabels = true;
|
|
2357
4466
|
const commit = (next) => {
|
|
2358
4467
|
setRows(next);
|
|
@@ -2363,20 +4472,20 @@ function EnumEditor({
|
|
|
2363
4472
|
};
|
|
2364
4473
|
const removeRow = (i) => commit(rows.filter((_, idx) => idx !== i));
|
|
2365
4474
|
const addRow = () => setRows([...rows, { value: "", label: "" }]);
|
|
2366
|
-
return /* @__PURE__ */
|
|
2367
|
-
/* @__PURE__ */
|
|
2368
|
-
/* @__PURE__ */
|
|
2369
|
-
/* @__PURE__ */
|
|
4475
|
+
return /* @__PURE__ */ jsxs10("div", { className: "space-y-3", children: [
|
|
4476
|
+
/* @__PURE__ */ jsxs10("div", { children: [
|
|
4477
|
+
/* @__PURE__ */ jsx12("p", { className: "text-sm font-medium", children: "Allowed values" }),
|
|
4478
|
+
/* @__PURE__ */ jsx12("p", { className: "text-xs text-muted-foreground", children: "Define a fixed set of choices. Leave empty for free input." })
|
|
2370
4479
|
] }),
|
|
2371
|
-
rows.length === 0 ? /* @__PURE__ */
|
|
2372
|
-
canEditLabels && /* @__PURE__ */
|
|
2373
|
-
/* @__PURE__ */
|
|
2374
|
-
/* @__PURE__ */
|
|
2375
|
-
/* @__PURE__ */
|
|
4480
|
+
rows.length === 0 ? /* @__PURE__ */ jsx12("p", { className: "text-xs text-muted-foreground", children: "No choices yet." }) : /* @__PURE__ */ jsxs10("div", { className: "space-y-2", children: [
|
|
4481
|
+
canEditLabels && /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2 px-1", children: [
|
|
4482
|
+
/* @__PURE__ */ jsx12("span", { className: "heading-meta flex-1", children: "Value" }),
|
|
4483
|
+
/* @__PURE__ */ jsx12("span", { className: "heading-meta flex-1", children: "Label (optional)" }),
|
|
4484
|
+
/* @__PURE__ */ jsx12("span", { className: "w-7" })
|
|
2376
4485
|
] }),
|
|
2377
|
-
rows.map((row, i) => /* @__PURE__ */
|
|
2378
|
-
/* @__PURE__ */
|
|
2379
|
-
|
|
4486
|
+
rows.map((row, i) => /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2", children: [
|
|
4487
|
+
/* @__PURE__ */ jsx12(
|
|
4488
|
+
Input4,
|
|
2380
4489
|
{
|
|
2381
4490
|
value: row.value,
|
|
2382
4491
|
onChange: (e) => updateRow(i, { value: e.target.value }),
|
|
@@ -2385,8 +4494,8 @@ function EnumEditor({
|
|
|
2385
4494
|
className: "h-8 text-sm flex-1"
|
|
2386
4495
|
}
|
|
2387
4496
|
),
|
|
2388
|
-
canEditLabels && /* @__PURE__ */
|
|
2389
|
-
|
|
4497
|
+
canEditLabels && /* @__PURE__ */ jsx12(
|
|
4498
|
+
Input4,
|
|
2390
4499
|
{
|
|
2391
4500
|
value: row.label ?? "",
|
|
2392
4501
|
onChange: (e) => updateRow(i, { label: e.target.value }),
|
|
@@ -2395,26 +4504,26 @@ function EnumEditor({
|
|
|
2395
4504
|
className: "h-8 text-sm flex-1"
|
|
2396
4505
|
}
|
|
2397
4506
|
),
|
|
2398
|
-
!state.readOnly && /* @__PURE__ */
|
|
4507
|
+
!state.readOnly && /* @__PURE__ */ jsx12(
|
|
2399
4508
|
IconButton3,
|
|
2400
4509
|
{
|
|
2401
4510
|
label: "Remove value",
|
|
2402
4511
|
tone: "destructive",
|
|
2403
4512
|
onClick: () => removeRow(i),
|
|
2404
|
-
children: /* @__PURE__ */
|
|
4513
|
+
children: /* @__PURE__ */ jsx12(Trash23, {})
|
|
2405
4514
|
}
|
|
2406
4515
|
)
|
|
2407
4516
|
] }, i))
|
|
2408
4517
|
] }),
|
|
2409
|
-
!state.readOnly && /* @__PURE__ */
|
|
2410
|
-
|
|
4518
|
+
!state.readOnly && /* @__PURE__ */ jsxs10(
|
|
4519
|
+
Button3,
|
|
2411
4520
|
{
|
|
2412
4521
|
variant: "outline",
|
|
2413
4522
|
size: "sm",
|
|
2414
4523
|
className: "gap-1.5",
|
|
2415
4524
|
onClick: addRow,
|
|
2416
4525
|
children: [
|
|
2417
|
-
/* @__PURE__ */
|
|
4526
|
+
/* @__PURE__ */ jsx12(Plus3, { className: "h-4 w-4" }),
|
|
2418
4527
|
"Add value"
|
|
2419
4528
|
]
|
|
2420
4529
|
}
|
|
@@ -2423,11 +4532,11 @@ function EnumEditor({
|
|
|
2423
4532
|
}
|
|
2424
4533
|
|
|
2425
4534
|
// src/inspector/TableHintsTab.tsx
|
|
2426
|
-
import { useMemo as
|
|
4535
|
+
import { useMemo as useMemo10 } from "react";
|
|
2427
4536
|
import { CELL_RENDERERS } from "@petrarca/sonnet-ui";
|
|
2428
|
-
import { JsonEditor as
|
|
2429
|
-
import { FormSelect as
|
|
2430
|
-
import { jsx as
|
|
4537
|
+
import { JsonEditor as JsonEditor3 } from "@petrarca/sonnet-ui/json-editor";
|
|
4538
|
+
import { FormSelect as FormSelect5, JsonSchemaFormRenderer as JsonSchemaFormRenderer6 } from "@petrarca/sonnet-forms";
|
|
4539
|
+
import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
2431
4540
|
var CELL_NAMES = Object.keys(CELL_RENDERERS);
|
|
2432
4541
|
var COLUMN_META_SCHEMA = {
|
|
2433
4542
|
title: "",
|
|
@@ -2444,7 +4553,7 @@ function TableHintsTab({
|
|
|
2444
4553
|
state
|
|
2445
4554
|
}) {
|
|
2446
4555
|
const hints = readTableHints(node.property);
|
|
2447
|
-
const cellOptions =
|
|
4556
|
+
const cellOptions = useMemo10(
|
|
2448
4557
|
() => JSON.stringify(hints.cellOptions ?? {}, null, 2),
|
|
2449
4558
|
[hints.cellOptions]
|
|
2450
4559
|
);
|
|
@@ -2467,9 +4576,9 @@ function TableHintsTab({
|
|
|
2467
4576
|
column.minWidth = v.minWidth;
|
|
2468
4577
|
commit({ column });
|
|
2469
4578
|
};
|
|
2470
|
-
return /* @__PURE__ */
|
|
2471
|
-
/* @__PURE__ */
|
|
2472
|
-
|
|
4579
|
+
return /* @__PURE__ */ jsxs11("div", { className: "space-y-4", children: [
|
|
4580
|
+
/* @__PURE__ */ jsx13(
|
|
4581
|
+
FormSelect5,
|
|
2473
4582
|
{
|
|
2474
4583
|
id: `${node.key}-cell`,
|
|
2475
4584
|
name: "cell",
|
|
@@ -2484,25 +4593,26 @@ function TableHintsTab({
|
|
|
2484
4593
|
clearable: false
|
|
2485
4594
|
}
|
|
2486
4595
|
),
|
|
2487
|
-
/* @__PURE__ */
|
|
2488
|
-
/* @__PURE__ */
|
|
2489
|
-
/* @__PURE__ */
|
|
4596
|
+
/* @__PURE__ */ jsxs11("div", { children: [
|
|
4597
|
+
/* @__PURE__ */ jsx13("p", { className: "heading-meta mb-2", children: "Column" }),
|
|
4598
|
+
/* @__PURE__ */ jsx13(
|
|
2490
4599
|
JsonSchemaFormRenderer6,
|
|
2491
4600
|
{
|
|
2492
4601
|
schema: COLUMN_META_SCHEMA,
|
|
2493
4602
|
data: columnValues,
|
|
2494
4603
|
onChange: onColumnChange,
|
|
4604
|
+
deferStateUpdates: true,
|
|
2495
4605
|
showActions: false,
|
|
2496
4606
|
showExtraProperties: false,
|
|
2497
4607
|
readOnly: state.readOnly
|
|
2498
4608
|
},
|
|
2499
|
-
node.path
|
|
4609
|
+
pathKey(node.path)
|
|
2500
4610
|
)
|
|
2501
4611
|
] }),
|
|
2502
|
-
/* @__PURE__ */
|
|
2503
|
-
/* @__PURE__ */
|
|
2504
|
-
/* @__PURE__ */
|
|
2505
|
-
|
|
4612
|
+
/* @__PURE__ */ jsxs11("div", { children: [
|
|
4613
|
+
/* @__PURE__ */ jsx13("p", { className: "heading-meta mb-2", children: "Cell options (JSON)" }),
|
|
4614
|
+
/* @__PURE__ */ jsx13(
|
|
4615
|
+
JsonEditor3,
|
|
2506
4616
|
{
|
|
2507
4617
|
value: cellOptions,
|
|
2508
4618
|
editablePaths: state.readOnly ? [] : ["/"],
|
|
@@ -2522,22 +4632,264 @@ function TableHintsTab({
|
|
|
2522
4632
|
] });
|
|
2523
4633
|
}
|
|
2524
4634
|
|
|
4635
|
+
// src/inspector/ConditionEditor.tsx
|
|
4636
|
+
import { useMemo as useMemo11 } from "react";
|
|
4637
|
+
import { FormSelect as FormSelect6 } from "@petrarca/sonnet-forms";
|
|
4638
|
+
import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
4639
|
+
function siblingFields(state, node) {
|
|
4640
|
+
const objectPath = node.path.slice(0, -1);
|
|
4641
|
+
const owner = objectPath.length === 0 ? { properties: state.schema.properties } : getProperty(state.schema, objectPath);
|
|
4642
|
+
return owner?.properties ?? {};
|
|
4643
|
+
}
|
|
4644
|
+
function ConditionEditor({
|
|
4645
|
+
node,
|
|
4646
|
+
state
|
|
4647
|
+
}) {
|
|
4648
|
+
const condition2 = useMemo11(
|
|
4649
|
+
() => ifToCondition(node.property.if),
|
|
4650
|
+
[node.property.if]
|
|
4651
|
+
);
|
|
4652
|
+
const fieldProps = useMemo11(() => siblingFields(state, node), [state, node]);
|
|
4653
|
+
const fields = useMemo11(() => Object.keys(fieldProps), [fieldProps]);
|
|
4654
|
+
const disabled = state.readOnly;
|
|
4655
|
+
const lastSeg = node.path[node.path.length - 1];
|
|
4656
|
+
const combinator = lastSeg?.kind === "condition" ? lastSeg.combinator : "allOf";
|
|
4657
|
+
const objectPath = node.path.slice(0, -1);
|
|
4658
|
+
const commit = (next) => {
|
|
4659
|
+
const ifSchema = conditionToIf(next, fieldProps[next.field]?.type);
|
|
4660
|
+
if (ifSchema) state.setCondition(node.path, ifSchema);
|
|
4661
|
+
};
|
|
4662
|
+
const fieldOptions = fields.map((f) => ({ value: f, label: f }));
|
|
4663
|
+
const needsValue = condition2.operator !== "exists";
|
|
4664
|
+
return /* @__PURE__ */ jsxs12("div", { className: "space-y-3", children: [
|
|
4665
|
+
/* @__PURE__ */ jsxs12("p", { className: "text-xs text-muted-foreground", children: [
|
|
4666
|
+
"Show the ",
|
|
4667
|
+
/* @__PURE__ */ jsx14("span", { className: "font-medium", children: "then" }),
|
|
4668
|
+
" fields when this condition holds (otherwise the ",
|
|
4669
|
+
/* @__PURE__ */ jsx14("span", { className: "font-medium", children: "else" }),
|
|
4670
|
+
" ",
|
|
4671
|
+
"fields, if any)."
|
|
4672
|
+
] }),
|
|
4673
|
+
/* @__PURE__ */ jsx14(
|
|
4674
|
+
FormSelect6,
|
|
4675
|
+
{
|
|
4676
|
+
id: "condition-match",
|
|
4677
|
+
name: "match",
|
|
4678
|
+
label: "Match (all conditions on this object)",
|
|
4679
|
+
options: [
|
|
4680
|
+
{ value: "allOf", label: "All (AND)" },
|
|
4681
|
+
{ value: "anyOf", label: "Any (OR)" }
|
|
4682
|
+
],
|
|
4683
|
+
value: combinator,
|
|
4684
|
+
onChange: (v) => state.setConditionalBehavior(
|
|
4685
|
+
objectPath,
|
|
4686
|
+
v === "anyOf" ? "anyOf" : "allOf"
|
|
4687
|
+
),
|
|
4688
|
+
clearable: false,
|
|
4689
|
+
disabled
|
|
4690
|
+
}
|
|
4691
|
+
),
|
|
4692
|
+
/* @__PURE__ */ jsx14(
|
|
4693
|
+
FormSelect6,
|
|
4694
|
+
{
|
|
4695
|
+
id: "condition-field",
|
|
4696
|
+
name: "field",
|
|
4697
|
+
label: "When property",
|
|
4698
|
+
options: fieldOptions,
|
|
4699
|
+
value: condition2.field || null,
|
|
4700
|
+
onChange: (v) => commit({ ...condition2, field: v ?? "" }),
|
|
4701
|
+
placeholder: "Select a property...",
|
|
4702
|
+
clearable: false,
|
|
4703
|
+
disabled
|
|
4704
|
+
}
|
|
4705
|
+
),
|
|
4706
|
+
/* @__PURE__ */ jsx14(
|
|
4707
|
+
FormSelect6,
|
|
4708
|
+
{
|
|
4709
|
+
id: "condition-operator",
|
|
4710
|
+
name: "operator",
|
|
4711
|
+
label: "Operator",
|
|
4712
|
+
options: OPERATOR_OPTIONS,
|
|
4713
|
+
value: condition2.operator,
|
|
4714
|
+
onChange: (v) => commit({ ...condition2, operator: v ?? "=" }),
|
|
4715
|
+
clearable: false,
|
|
4716
|
+
disabled
|
|
4717
|
+
}
|
|
4718
|
+
),
|
|
4719
|
+
needsValue && /* @__PURE__ */ jsx14(
|
|
4720
|
+
OperatorValueInput,
|
|
4721
|
+
{
|
|
4722
|
+
id: "condition-value",
|
|
4723
|
+
cacheKey: `${node.path.length}:${condition2.field}:${condition2.operator}`,
|
|
4724
|
+
value: condition2.value,
|
|
4725
|
+
onCommit: (v) => commit({ ...condition2, value: v }),
|
|
4726
|
+
disabled: disabled || !condition2.field
|
|
4727
|
+
}
|
|
4728
|
+
)
|
|
4729
|
+
] });
|
|
4730
|
+
}
|
|
4731
|
+
|
|
4732
|
+
// src/inspector/RuleEditor.tsx
|
|
4733
|
+
import { useMemo as useMemo12 } from "react";
|
|
4734
|
+
|
|
4735
|
+
// src/inspector/ruleScopes.ts
|
|
4736
|
+
function dataPointerPrefix(node) {
|
|
4737
|
+
let prefix = "";
|
|
4738
|
+
for (let i = 0; i < node.path.length - 1; i++) {
|
|
4739
|
+
const seg = node.path[i];
|
|
4740
|
+
if (seg.kind !== "prop") return void 0;
|
|
4741
|
+
prefix += `/${seg.key}`;
|
|
4742
|
+
}
|
|
4743
|
+
return prefix;
|
|
4744
|
+
}
|
|
4745
|
+
function scopePointers(state, node) {
|
|
4746
|
+
const options = /* @__PURE__ */ new Map();
|
|
4747
|
+
const prefix = dataPointerPrefix(node);
|
|
4748
|
+
const selfPointer = prefix === void 0 ? void 0 : `${prefix}/${node.key}`;
|
|
4749
|
+
const add = (pointer, prop2) => {
|
|
4750
|
+
if (pointer === selfPointer) return;
|
|
4751
|
+
if (!options.has(pointer))
|
|
4752
|
+
options.set(pointer, { pointer, type: prop2.type });
|
|
4753
|
+
};
|
|
4754
|
+
for (const [k, prop2] of Object.entries(state.schema.properties ?? {})) {
|
|
4755
|
+
add(`/${k}`, prop2);
|
|
4756
|
+
}
|
|
4757
|
+
if (prefix) {
|
|
4758
|
+
const siblings = findParentProperties(state, node);
|
|
4759
|
+
for (const [k, prop2] of Object.entries(siblings ?? {})) {
|
|
4760
|
+
add(`${prefix}/${k}`, prop2);
|
|
4761
|
+
}
|
|
4762
|
+
}
|
|
4763
|
+
return [...options.values()];
|
|
4764
|
+
}
|
|
4765
|
+
function findParentProperties(state, node) {
|
|
4766
|
+
let props = state.schema.properties;
|
|
4767
|
+
for (let i = 0; i < node.path.length - 1; i++) {
|
|
4768
|
+
const seg = node.path[i];
|
|
4769
|
+
if (seg.kind !== "prop" || !props) return void 0;
|
|
4770
|
+
const next = props[seg.key];
|
|
4771
|
+
props = next?.type === "array" ? next.items?.properties : next?.properties;
|
|
4772
|
+
if (!props) return void 0;
|
|
4773
|
+
}
|
|
4774
|
+
return props;
|
|
4775
|
+
}
|
|
4776
|
+
|
|
4777
|
+
// src/inspector/RuleEditor.tsx
|
|
4778
|
+
import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
4779
|
+
function RuleEditor({
|
|
4780
|
+
node,
|
|
4781
|
+
state
|
|
4782
|
+
}) {
|
|
4783
|
+
const scopes = useMemo12(() => scopePointers(state, node), [state, node]);
|
|
4784
|
+
return /* @__PURE__ */ jsxs13("div", { className: "space-y-3", children: [
|
|
4785
|
+
/* @__PURE__ */ jsx15("p", { className: "text-xs text-muted-foreground", children: "Toggle this field's visibility (or enabled state) based on another field's value. The field keeps its position in the form." }),
|
|
4786
|
+
/* @__PURE__ */ jsx15(
|
|
4787
|
+
RuleFields,
|
|
4788
|
+
{
|
|
4789
|
+
rule: node.property["x-ui-rule"],
|
|
4790
|
+
scopes,
|
|
4791
|
+
effects: FIELD_EFFECT_OPTIONS,
|
|
4792
|
+
idPrefix: "rule",
|
|
4793
|
+
disabled: state.readOnly,
|
|
4794
|
+
onChange: (rule) => state.updateProperty(node.path, { "x-ui-rule": rule })
|
|
4795
|
+
}
|
|
4796
|
+
)
|
|
4797
|
+
] });
|
|
4798
|
+
}
|
|
4799
|
+
|
|
4800
|
+
// src/inspector/ReferenceTab.tsx
|
|
4801
|
+
import { useMemo as useMemo13, useRef as useRef5 } from "react";
|
|
4802
|
+
import { JsonSchemaFormRenderer as JsonSchemaFormRenderer7 } from "@petrarca/sonnet-forms";
|
|
4803
|
+
import { Button as Button4 } from "@petrarca/sonnet-ui";
|
|
4804
|
+
import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
4805
|
+
function toOverrides(prop2, required) {
|
|
4806
|
+
return {
|
|
4807
|
+
title: prop2.title,
|
|
4808
|
+
description: prop2.description,
|
|
4809
|
+
// Coerce to string for the text field; non-string defaults (number, boolean)
|
|
4810
|
+
// are serialised and written back as strings -- v1 limitation.
|
|
4811
|
+
default: prop2.default == null ? void 0 : String(prop2.default),
|
|
4812
|
+
required
|
|
4813
|
+
};
|
|
4814
|
+
}
|
|
4815
|
+
function toPatch(values) {
|
|
4816
|
+
const str = (v) => typeof v === "string" && v.length > 0 ? v : void 0;
|
|
4817
|
+
return {
|
|
4818
|
+
title: str(values.title),
|
|
4819
|
+
description: str(values.description),
|
|
4820
|
+
default: str(values.default)
|
|
4821
|
+
};
|
|
4822
|
+
}
|
|
4823
|
+
function ReferenceTab({
|
|
4824
|
+
node,
|
|
4825
|
+
state
|
|
4826
|
+
}) {
|
|
4827
|
+
const schema = useMemo13(() => buildReferenceMetaSchema(), []);
|
|
4828
|
+
const values = useMemo13(
|
|
4829
|
+
() => toOverrides(node.property, node.required),
|
|
4830
|
+
[node.property, node.required]
|
|
4831
|
+
);
|
|
4832
|
+
const prevRef = useRef5(values);
|
|
4833
|
+
prevRef.current = values;
|
|
4834
|
+
const handleChange = (data) => {
|
|
4835
|
+
const next = data;
|
|
4836
|
+
const prev = prevRef.current;
|
|
4837
|
+
if (next.required !== prev.required) {
|
|
4838
|
+
state.setRequired(node.path, !!next.required);
|
|
4839
|
+
return;
|
|
4840
|
+
}
|
|
4841
|
+
state.updateProperty(node.path, toPatch(next));
|
|
4842
|
+
};
|
|
4843
|
+
return /* @__PURE__ */ jsxs14("div", { className: "space-y-4", children: [
|
|
4844
|
+
/* @__PURE__ */ jsxs14("p", { className: "text-sm text-muted-foreground", children: [
|
|
4845
|
+
"References ",
|
|
4846
|
+
/* @__PURE__ */ jsx16("span", { className: "font-medium", children: node.reference }),
|
|
4847
|
+
". Its fields come from a shared definition (read-only here -- edit the definition to change all usages). The values below override the label, help text, and default for this usage only."
|
|
4848
|
+
] }),
|
|
4849
|
+
/* @__PURE__ */ jsx16(
|
|
4850
|
+
JsonSchemaFormRenderer7,
|
|
4851
|
+
{
|
|
4852
|
+
schema,
|
|
4853
|
+
data: values,
|
|
4854
|
+
onChange: handleChange,
|
|
4855
|
+
deferStateUpdates: true,
|
|
4856
|
+
showActions: false,
|
|
4857
|
+
showExtraProperties: false,
|
|
4858
|
+
readOnly: state.readOnly
|
|
4859
|
+
},
|
|
4860
|
+
pathKey(node.path)
|
|
4861
|
+
),
|
|
4862
|
+
!state.readOnly && state.canEditProperty(node.path) && /* @__PURE__ */ jsxs14("div", { className: "border-t pt-3", children: [
|
|
4863
|
+
/* @__PURE__ */ jsx16(
|
|
4864
|
+
Button4,
|
|
4865
|
+
{
|
|
4866
|
+
variant: "outline",
|
|
4867
|
+
size: "sm",
|
|
4868
|
+
onClick: () => state.changePropertyType(node.path, "string"),
|
|
4869
|
+
children: "Stop referencing"
|
|
4870
|
+
}
|
|
4871
|
+
),
|
|
4872
|
+
/* @__PURE__ */ jsx16("p", { className: "mt-1 text-xs text-muted-foreground", children: "Removes the reference and turns this into an editable property." })
|
|
4873
|
+
] })
|
|
4874
|
+
] });
|
|
4875
|
+
}
|
|
4876
|
+
|
|
2525
4877
|
// src/inspector/Inspector.tsx
|
|
2526
|
-
import { jsx as
|
|
4878
|
+
import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
2527
4879
|
var ENUM_CAPABLE_TYPES = /* @__PURE__ */ new Set(["string", "number", "integer"]);
|
|
2528
4880
|
function KeyField({
|
|
2529
4881
|
fieldKey,
|
|
2530
4882
|
onRename,
|
|
2531
4883
|
readOnly
|
|
2532
4884
|
}) {
|
|
2533
|
-
const [editing, setEditing] =
|
|
2534
|
-
const [value, setValue] =
|
|
2535
|
-
const ref =
|
|
2536
|
-
|
|
4885
|
+
const [editing, setEditing] = useState8(false);
|
|
4886
|
+
const [value, setValue] = useState8(fieldKey);
|
|
4887
|
+
const ref = useRef6(null);
|
|
4888
|
+
useEffect5(() => {
|
|
2537
4889
|
setValue(fieldKey);
|
|
2538
4890
|
setEditing(false);
|
|
2539
4891
|
}, [fieldKey]);
|
|
2540
|
-
|
|
4892
|
+
useEffect5(() => {
|
|
2541
4893
|
if (editing) {
|
|
2542
4894
|
ref.current?.focus();
|
|
2543
4895
|
ref.current?.select();
|
|
@@ -2553,14 +4905,14 @@ function KeyField({
|
|
|
2553
4905
|
setEditing(false);
|
|
2554
4906
|
};
|
|
2555
4907
|
if (!editing) {
|
|
2556
|
-
return /* @__PURE__ */
|
|
2557
|
-
/* @__PURE__ */
|
|
2558
|
-
!readOnly && /* @__PURE__ */
|
|
4908
|
+
return /* @__PURE__ */ jsxs15("div", { className: "flex items-center gap-1", children: [
|
|
4909
|
+
/* @__PURE__ */ jsx17("span", { className: "text-sm font-semibold truncate", children: fieldKey }),
|
|
4910
|
+
!readOnly && /* @__PURE__ */ jsx17(IconButton4, { label: "Rename property", onClick: () => setEditing(true), children: /* @__PURE__ */ jsx17(Pencil, {}) })
|
|
2559
4911
|
] });
|
|
2560
4912
|
}
|
|
2561
|
-
return /* @__PURE__ */
|
|
2562
|
-
/* @__PURE__ */
|
|
2563
|
-
|
|
4913
|
+
return /* @__PURE__ */ jsxs15("div", { className: "flex items-center gap-1", children: [
|
|
4914
|
+
/* @__PURE__ */ jsx17(
|
|
4915
|
+
Input5,
|
|
2564
4916
|
{
|
|
2565
4917
|
ref,
|
|
2566
4918
|
value,
|
|
@@ -2577,8 +4929,8 @@ function KeyField({
|
|
|
2577
4929
|
className: "h-7 text-sm font-medium"
|
|
2578
4930
|
}
|
|
2579
4931
|
),
|
|
2580
|
-
/* @__PURE__ */
|
|
2581
|
-
/* @__PURE__ */
|
|
4932
|
+
/* @__PURE__ */ jsx17(IconButton4, { label: "Confirm rename", onClick: commit, children: /* @__PURE__ */ jsx17(Check2, {}) }),
|
|
4933
|
+
/* @__PURE__ */ jsx17(IconButton4, { label: "Cancel rename", onClick: cancel, children: /* @__PURE__ */ jsx17(X, {}) })
|
|
2582
4934
|
] });
|
|
2583
4935
|
}
|
|
2584
4936
|
function findNode(nodes, path) {
|
|
@@ -2589,49 +4941,109 @@ function findNode(nodes, path) {
|
|
|
2589
4941
|
}
|
|
2590
4942
|
return void 0;
|
|
2591
4943
|
}
|
|
4944
|
+
function InspectorHeader({
|
|
4945
|
+
node,
|
|
4946
|
+
state
|
|
4947
|
+
}) {
|
|
4948
|
+
const isDefinition = node.kind === "definition";
|
|
4949
|
+
const noRename = isDefinition ? state.capabilities.locked : state.readOnly || node.slotKind === "branch" || node.slotKind === "conditional";
|
|
4950
|
+
const onRename = (next) => isDefinition ? state.renameDefinition(node.key, next) : state.renameProperty(node.path, next);
|
|
4951
|
+
return /* @__PURE__ */ jsxs15("div", { className: "mb-3 space-y-1", children: [
|
|
4952
|
+
/* @__PURE__ */ jsx17(
|
|
4953
|
+
KeyField,
|
|
4954
|
+
{
|
|
4955
|
+
fieldKey: node.key,
|
|
4956
|
+
readOnly: noRename,
|
|
4957
|
+
onRename
|
|
4958
|
+
},
|
|
4959
|
+
pathKey(node.path)
|
|
4960
|
+
),
|
|
4961
|
+
/* @__PURE__ */ jsx17("p", { className: "font-mono text-xs text-muted-foreground", children: isDefinition ? "definition" : node.typeLabel })
|
|
4962
|
+
] });
|
|
4963
|
+
}
|
|
4964
|
+
function PropertyTabs({
|
|
4965
|
+
node,
|
|
4966
|
+
state,
|
|
4967
|
+
widgetOptions
|
|
4968
|
+
}) {
|
|
4969
|
+
const enumCapable = ENUM_CAPABLE_TYPES.has(node.property.type ?? "");
|
|
4970
|
+
const ruleUnsupported = node.path.some(
|
|
4971
|
+
(s) => s.kind === "branch" || s.kind === "condition" || s.kind === "slot"
|
|
4972
|
+
);
|
|
4973
|
+
const uiUnsupported = node.path.some((s) => s.kind !== "prop");
|
|
4974
|
+
return /* @__PURE__ */ jsxs15(Tabs2, { defaultValue: "data", children: [
|
|
4975
|
+
/* @__PURE__ */ jsxs15(TabsList2, { className: "mb-3", children: [
|
|
4976
|
+
/* @__PURE__ */ jsx17(TabsTrigger2, { value: "data", children: "Data" }),
|
|
4977
|
+
enumCapable && /* @__PURE__ */ jsx17(TabsTrigger2, { value: "enum", children: "Enum" }),
|
|
4978
|
+
/* @__PURE__ */ jsx17(TabsTrigger2, { value: "ui", children: "UI" }),
|
|
4979
|
+
!ruleUnsupported && /* @__PURE__ */ jsx17(TabsTrigger2, { value: "rule", children: "Rule" }),
|
|
4980
|
+
/* @__PURE__ */ jsx17(TabsTrigger2, { value: "table", children: "Table" })
|
|
4981
|
+
] }),
|
|
4982
|
+
/* @__PURE__ */ jsx17(TabsContent2, { value: "data", children: /* @__PURE__ */ jsx17(DataTab, { node, state }) }),
|
|
4983
|
+
enumCapable && /* @__PURE__ */ jsx17(TabsContent2, { value: "enum", children: /* @__PURE__ */ jsx17(EnumEditor, { node, state }) }),
|
|
4984
|
+
!ruleUnsupported && /* @__PURE__ */ jsx17(TabsContent2, { value: "rule", children: /* @__PURE__ */ jsx17(RuleEditor, { node, state }) }),
|
|
4985
|
+
/* @__PURE__ */ jsx17(TabsContent2, { value: "ui", children: uiUnsupported ? /* @__PURE__ */ jsx17("p", { className: "text-sm text-muted-foreground p-2", children: "Widget hints for properties inside a variant branch or definition are not yet editable here. Author them in the JSON Schema or UI Schema tab." }) : /* @__PURE__ */ jsx17(UITab, { node, state, widgetOptions }) }),
|
|
4986
|
+
/* @__PURE__ */ jsx17(TabsContent2, { value: "table", children: /* @__PURE__ */ jsx17(TableHintsTab, { node, state }) })
|
|
4987
|
+
] }, `${pathKey(node.path)}:${enumCapable}`);
|
|
4988
|
+
}
|
|
4989
|
+
function InspectorBody({
|
|
4990
|
+
node,
|
|
4991
|
+
state,
|
|
4992
|
+
widgetOptions
|
|
4993
|
+
}) {
|
|
4994
|
+
const lastSeg = node.path[node.path.length - 1];
|
|
4995
|
+
if (node.kind === "reference") {
|
|
4996
|
+
return /* @__PURE__ */ jsx17(ReferenceTab, { node, state });
|
|
4997
|
+
}
|
|
4998
|
+
if (node.readOnly) {
|
|
4999
|
+
return /* @__PURE__ */ jsx17("p", { className: "text-sm text-muted-foreground p-2", children: "This field comes from a shared definition and is read-only here. Edit the definition in the JSON Schema tab to change it." });
|
|
5000
|
+
}
|
|
5001
|
+
if (node.kind === "definition") {
|
|
5002
|
+
return /* @__PURE__ */ jsxs15("p", { className: "text-sm text-muted-foreground p-2", children: [
|
|
5003
|
+
"Reusable definition ",
|
|
5004
|
+
/* @__PURE__ */ jsx17("span", { className: "font-medium", children: node.key }),
|
|
5005
|
+
". Add or edit its fields in the tree; reference it from a property to reuse its shape. Renaming it here updates every reference."
|
|
5006
|
+
] });
|
|
5007
|
+
}
|
|
5008
|
+
if (node.kind === "conditional") {
|
|
5009
|
+
return /* @__PURE__ */ jsx17(ConditionEditor, { node, state });
|
|
5010
|
+
}
|
|
5011
|
+
if (node.slotKind === "conditional" && lastSeg?.kind === "slot") {
|
|
5012
|
+
return /* @__PURE__ */ jsxs15("p", { className: "text-sm text-muted-foreground p-2", children: [
|
|
5013
|
+
"Add fields to this ",
|
|
5014
|
+
/* @__PURE__ */ jsx17("span", { className: "font-medium", children: node.key }),
|
|
5015
|
+
" ",
|
|
5016
|
+
"branch from the tree. They apply when the condition",
|
|
5017
|
+
" ",
|
|
5018
|
+
node.key === "then" ? "holds" : "does not hold",
|
|
5019
|
+
"."
|
|
5020
|
+
] });
|
|
5021
|
+
}
|
|
5022
|
+
return /* @__PURE__ */ jsx17(PropertyTabs, { node, state, widgetOptions });
|
|
5023
|
+
}
|
|
2592
5024
|
function Inspector({
|
|
2593
5025
|
state,
|
|
2594
5026
|
widgetOptions
|
|
2595
5027
|
}) {
|
|
2596
|
-
const tree =
|
|
5028
|
+
const tree = useMemo14(
|
|
5029
|
+
() => buildPropertyTree(state.schema, state.refResolver),
|
|
5030
|
+
[state.schema, state.refResolver]
|
|
5031
|
+
);
|
|
2597
5032
|
const node = state.selection ? findNode(tree, state.selection) : void 0;
|
|
2598
5033
|
if (!node) {
|
|
2599
|
-
return /* @__PURE__ */
|
|
5034
|
+
return /* @__PURE__ */ jsx17("p", { className: "text-sm text-muted-foreground", children: "Select a property to edit." });
|
|
2600
5035
|
}
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
/* @__PURE__ */
|
|
2604
|
-
/* @__PURE__ */ jsx11(
|
|
2605
|
-
KeyField,
|
|
2606
|
-
{
|
|
2607
|
-
fieldKey: node.key,
|
|
2608
|
-
readOnly: state.readOnly,
|
|
2609
|
-
onRename: (next) => state.renameProperty(node.path, next)
|
|
2610
|
-
},
|
|
2611
|
-
node.path.join(".")
|
|
2612
|
-
),
|
|
2613
|
-
/* @__PURE__ */ jsx11("p", { className: "font-mono text-xs text-muted-foreground", children: node.typeLabel })
|
|
2614
|
-
] }),
|
|
2615
|
-
/* @__PURE__ */ jsxs9(Tabs2, { defaultValue: "data", children: [
|
|
2616
|
-
/* @__PURE__ */ jsxs9(TabsList2, { className: "mb-3", children: [
|
|
2617
|
-
/* @__PURE__ */ jsx11(TabsTrigger2, { value: "data", children: "Data" }),
|
|
2618
|
-
enumCapable && /* @__PURE__ */ jsx11(TabsTrigger2, { value: "enum", children: "Enum" }),
|
|
2619
|
-
/* @__PURE__ */ jsx11(TabsTrigger2, { value: "ui", children: "UI" }),
|
|
2620
|
-
/* @__PURE__ */ jsx11(TabsTrigger2, { value: "table", children: "Table" })
|
|
2621
|
-
] }),
|
|
2622
|
-
/* @__PURE__ */ jsx11(TabsContent2, { value: "data", children: /* @__PURE__ */ jsx11(DataTab, { node, state }) }),
|
|
2623
|
-
enumCapable && /* @__PURE__ */ jsx11(TabsContent2, { value: "enum", children: /* @__PURE__ */ jsx11(EnumEditor, { node, state }) }),
|
|
2624
|
-
/* @__PURE__ */ jsx11(TabsContent2, { value: "ui", children: /* @__PURE__ */ jsx11(UITab, { node, state, widgetOptions }) }),
|
|
2625
|
-
/* @__PURE__ */ jsx11(TabsContent2, { value: "table", children: /* @__PURE__ */ jsx11(TableHintsTab, { node, state }) })
|
|
2626
|
-
] }, `${node.path.join(".")}:${enumCapable}`)
|
|
5036
|
+
return /* @__PURE__ */ jsxs15("div", { children: [
|
|
5037
|
+
/* @__PURE__ */ jsx17(InspectorHeader, { node, state }),
|
|
5038
|
+
/* @__PURE__ */ jsx17(InspectorBody, { node, state, widgetOptions })
|
|
2627
5039
|
] });
|
|
2628
5040
|
}
|
|
2629
5041
|
|
|
2630
5042
|
// src/SchemaDesigner.tsx
|
|
2631
|
-
import { Fragment as
|
|
5043
|
+
import { Fragment as Fragment5, jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
2632
5044
|
function SchemaDesigner(props) {
|
|
2633
|
-
const [capabilities, setCapabilities] =
|
|
2634
|
-
|
|
5045
|
+
const [capabilities, setCapabilities] = useState9(props.capabilities);
|
|
5046
|
+
useEffect6(() => {
|
|
2635
5047
|
setCapabilities(props.capabilities);
|
|
2636
5048
|
}, [props.capabilities]);
|
|
2637
5049
|
const state = useDesignerState({ ...props, capabilities });
|
|
@@ -2657,17 +5069,17 @@ function SchemaDesigner(props) {
|
|
|
2657
5069
|
maxWidth: 520,
|
|
2658
5070
|
direction: "right-edge"
|
|
2659
5071
|
});
|
|
2660
|
-
const [inspectorCollapsed, setInspectorCollapsed] =
|
|
2661
|
-
const [treeCollapsed, setTreeCollapsed] =
|
|
2662
|
-
const [settingsOpen, setSettingsOpen] =
|
|
2663
|
-
return /* @__PURE__ */
|
|
2664
|
-
!treeCollapsed && /* @__PURE__ */
|
|
2665
|
-
/* @__PURE__ */
|
|
5072
|
+
const [inspectorCollapsed, setInspectorCollapsed] = useState9(false);
|
|
5073
|
+
const [treeCollapsed, setTreeCollapsed] = useState9(false);
|
|
5074
|
+
const [settingsOpen, setSettingsOpen] = useState9(false);
|
|
5075
|
+
return /* @__PURE__ */ jsxs16("div", { className: "flex w-full h-full overflow-hidden border rounded-lg bg-background", children: [
|
|
5076
|
+
!treeCollapsed && /* @__PURE__ */ jsxs16(Fragment5, { children: [
|
|
5077
|
+
/* @__PURE__ */ jsx18(
|
|
2666
5078
|
"div",
|
|
2667
5079
|
{
|
|
2668
5080
|
style: state.capabilities.showPreview ? { width: treeWidth } : void 0,
|
|
2669
|
-
className: state.capabilities.showPreview ? "shrink-0 overflow-
|
|
2670
|
-
children: /* @__PURE__ */
|
|
5081
|
+
className: state.capabilities.showPreview ? "shrink-0 overflow-hidden p-3 flex flex-col min-h-0" : "flex-1 min-w-0 overflow-hidden p-3 flex flex-col min-h-0",
|
|
5082
|
+
children: /* @__PURE__ */ jsx18(
|
|
2671
5083
|
PropertyTree,
|
|
2672
5084
|
{
|
|
2673
5085
|
state,
|
|
@@ -2676,7 +5088,7 @@ function SchemaDesigner(props) {
|
|
|
2676
5088
|
)
|
|
2677
5089
|
}
|
|
2678
5090
|
),
|
|
2679
|
-
state.capabilities.showPreview && /* @__PURE__ */
|
|
5091
|
+
state.capabilities.showPreview && /* @__PURE__ */ jsx18(
|
|
2680
5092
|
ResizeHandle,
|
|
2681
5093
|
{
|
|
2682
5094
|
orientation: "vertical",
|
|
@@ -2686,15 +5098,15 @@ function SchemaDesigner(props) {
|
|
|
2686
5098
|
}
|
|
2687
5099
|
)
|
|
2688
5100
|
] }),
|
|
2689
|
-
treeCollapsed && /* @__PURE__ */
|
|
5101
|
+
treeCollapsed && /* @__PURE__ */ jsx18("div", { className: "shrink-0 border-r flex flex-col items-center gap-1 p-1", children: /* @__PURE__ */ jsx18(
|
|
2690
5102
|
IconButton5,
|
|
2691
5103
|
{
|
|
2692
5104
|
label: "Expand properties",
|
|
2693
5105
|
onClick: () => setTreeCollapsed(false),
|
|
2694
|
-
children: /* @__PURE__ */
|
|
5106
|
+
children: /* @__PURE__ */ jsx18(PanelLeftOpen, {})
|
|
2695
5107
|
}
|
|
2696
5108
|
) }),
|
|
2697
|
-
state.capabilities.showPreview && /* @__PURE__ */
|
|
5109
|
+
state.capabilities.showPreview && /* @__PURE__ */ jsx18("div", { className: "flex-1 min-w-0 overflow-hidden p-4 flex flex-col min-h-0", children: /* @__PURE__ */ jsx18(
|
|
2698
5110
|
PreviewPane,
|
|
2699
5111
|
{
|
|
2700
5112
|
state,
|
|
@@ -2702,8 +5114,8 @@ function SchemaDesigner(props) {
|
|
|
2702
5114
|
onCapabilitiesChange: setCapabilities
|
|
2703
5115
|
}
|
|
2704
5116
|
) }),
|
|
2705
|
-
!inspectorCollapsed && /* @__PURE__ */
|
|
2706
|
-
/* @__PURE__ */
|
|
5117
|
+
!inspectorCollapsed && /* @__PURE__ */ jsxs16(Fragment5, { children: [
|
|
5118
|
+
/* @__PURE__ */ jsx18(
|
|
2707
5119
|
ResizeHandle,
|
|
2708
5120
|
{
|
|
2709
5121
|
orientation: "vertical",
|
|
@@ -2712,57 +5124,57 @@ function SchemaDesigner(props) {
|
|
|
2712
5124
|
onDoubleClick: resetInspectorWidth
|
|
2713
5125
|
}
|
|
2714
5126
|
),
|
|
2715
|
-
/* @__PURE__ */
|
|
5127
|
+
/* @__PURE__ */ jsxs16(
|
|
2716
5128
|
"div",
|
|
2717
5129
|
{
|
|
2718
5130
|
style: { width: inspectorWidth },
|
|
2719
5131
|
className: "shrink-0 border-l overflow-auto p-4 bg-card",
|
|
2720
5132
|
children: [
|
|
2721
|
-
/* @__PURE__ */
|
|
2722
|
-
/* @__PURE__ */
|
|
2723
|
-
/* @__PURE__ */
|
|
2724
|
-
/* @__PURE__ */
|
|
5133
|
+
/* @__PURE__ */ jsxs16("div", { className: "flex items-center gap-1 mb-2", children: [
|
|
5134
|
+
/* @__PURE__ */ jsx18("p", { className: "heading-meta", children: "Inspector" }),
|
|
5135
|
+
/* @__PURE__ */ jsxs16("span", { className: "ml-auto flex gap-0.5", children: [
|
|
5136
|
+
/* @__PURE__ */ jsx18(
|
|
2725
5137
|
IconButton5,
|
|
2726
5138
|
{
|
|
2727
5139
|
label: "Form settings",
|
|
2728
5140
|
onClick: () => setSettingsOpen(true),
|
|
2729
|
-
children: /* @__PURE__ */
|
|
5141
|
+
children: /* @__PURE__ */ jsx18(Settings, {})
|
|
2730
5142
|
}
|
|
2731
5143
|
),
|
|
2732
|
-
/* @__PURE__ */
|
|
5144
|
+
/* @__PURE__ */ jsx18(
|
|
2733
5145
|
IconButton5,
|
|
2734
5146
|
{
|
|
2735
5147
|
label: "Collapse inspector",
|
|
2736
5148
|
onClick: () => setInspectorCollapsed(true),
|
|
2737
|
-
children: /* @__PURE__ */
|
|
5149
|
+
children: /* @__PURE__ */ jsx18(PanelRightClose, {})
|
|
2738
5150
|
}
|
|
2739
5151
|
)
|
|
2740
5152
|
] })
|
|
2741
5153
|
] }),
|
|
2742
|
-
/* @__PURE__ */
|
|
5154
|
+
/* @__PURE__ */ jsx18(Inspector, { state, widgetOptions: props.widgetOptions })
|
|
2743
5155
|
]
|
|
2744
5156
|
}
|
|
2745
5157
|
)
|
|
2746
5158
|
] }),
|
|
2747
|
-
inspectorCollapsed && /* @__PURE__ */
|
|
2748
|
-
/* @__PURE__ */
|
|
5159
|
+
inspectorCollapsed && /* @__PURE__ */ jsxs16("div", { className: "shrink-0 border-l flex flex-col items-center gap-1 p-1", children: [
|
|
5160
|
+
/* @__PURE__ */ jsx18(
|
|
2749
5161
|
IconButton5,
|
|
2750
5162
|
{
|
|
2751
5163
|
label: "Expand inspector",
|
|
2752
5164
|
onClick: () => setInspectorCollapsed(false),
|
|
2753
|
-
children: /* @__PURE__ */
|
|
5165
|
+
children: /* @__PURE__ */ jsx18(PanelRightOpen, {})
|
|
2754
5166
|
}
|
|
2755
5167
|
),
|
|
2756
|
-
/* @__PURE__ */
|
|
5168
|
+
/* @__PURE__ */ jsx18(
|
|
2757
5169
|
IconButton5,
|
|
2758
5170
|
{
|
|
2759
5171
|
label: "Form settings",
|
|
2760
5172
|
onClick: () => setSettingsOpen(true),
|
|
2761
|
-
children: /* @__PURE__ */
|
|
5173
|
+
children: /* @__PURE__ */ jsx18(Settings, {})
|
|
2762
5174
|
}
|
|
2763
5175
|
)
|
|
2764
5176
|
] }),
|
|
2765
|
-
/* @__PURE__ */
|
|
5177
|
+
/* @__PURE__ */ jsx18(
|
|
2766
5178
|
FormSettingsSheet,
|
|
2767
5179
|
{
|
|
2768
5180
|
open: settingsOpen,
|
|
@@ -2784,41 +5196,77 @@ export {
|
|
|
2784
5196
|
TableHintsTab,
|
|
2785
5197
|
TablePreview,
|
|
2786
5198
|
UITab,
|
|
5199
|
+
addBranch,
|
|
5200
|
+
addConditional,
|
|
5201
|
+
addDefinition,
|
|
5202
|
+
addElse,
|
|
5203
|
+
addLayoutGroup,
|
|
2787
5204
|
addProperty,
|
|
5205
|
+
branch,
|
|
2788
5206
|
buildPropertyTree,
|
|
2789
5207
|
capabilitiesSchema,
|
|
2790
5208
|
changePropertyType,
|
|
2791
5209
|
childContainer,
|
|
2792
|
-
|
|
5210
|
+
childSlots,
|
|
2793
5211
|
compatibleWidgets,
|
|
5212
|
+
condition,
|
|
5213
|
+
def,
|
|
5214
|
+
definitionUsageCount,
|
|
5215
|
+
definitionsOf,
|
|
2794
5216
|
deleteProperty,
|
|
2795
5217
|
embedUiSchema,
|
|
2796
5218
|
getProperty,
|
|
2797
5219
|
getScopedLayout,
|
|
2798
5220
|
getUiEntryAtPath,
|
|
2799
5221
|
isContainer,
|
|
5222
|
+
isReservedKey,
|
|
2800
5223
|
layoutScopes,
|
|
5224
|
+
moveBranch,
|
|
5225
|
+
moveLayoutGroup,
|
|
2801
5226
|
moveProperty,
|
|
5227
|
+
newBranch,
|
|
5228
|
+
newCondition,
|
|
2802
5229
|
newProperty,
|
|
2803
5230
|
orderedKeys,
|
|
5231
|
+
parsePathKey,
|
|
2804
5232
|
pathKey,
|
|
5233
|
+
placeFieldInGroup,
|
|
5234
|
+
prop,
|
|
2805
5235
|
propertyKind,
|
|
2806
5236
|
readEnumChoices,
|
|
2807
5237
|
readTableHints,
|
|
5238
|
+
removeBranch,
|
|
5239
|
+
removeConditional,
|
|
5240
|
+
removeDefinition,
|
|
5241
|
+
removeElse,
|
|
5242
|
+
removeLayoutGroup,
|
|
5243
|
+
renameDefinition,
|
|
5244
|
+
renameLayoutGroup,
|
|
2808
5245
|
renameProperty,
|
|
2809
5246
|
resolveCapabilities,
|
|
2810
5247
|
samePath,
|
|
5248
|
+
sameSegment,
|
|
2811
5249
|
sampleRows,
|
|
5250
|
+
schemaAsRootProp,
|
|
5251
|
+
setCondition,
|
|
5252
|
+
setConditionalBehavior,
|
|
2812
5253
|
setEnum,
|
|
2813
5254
|
setGlobalOptions,
|
|
5255
|
+
setLayoutGroupRule,
|
|
2814
5256
|
setPropertyLayout,
|
|
5257
|
+
setReference,
|
|
2815
5258
|
setRequired,
|
|
2816
5259
|
setRootLayout,
|
|
2817
5260
|
setRootLayoutRows,
|
|
5261
|
+
setScopedGroupRows,
|
|
5262
|
+
setScopedLayoutGroups,
|
|
5263
|
+
setScopedLayoutMode,
|
|
2818
5264
|
setScopedLayoutOptions,
|
|
2819
5265
|
setScopedLayoutRows,
|
|
5266
|
+
setScopedLayoutVariant,
|
|
2820
5267
|
setTableHints,
|
|
2821
5268
|
setUiEntry,
|
|
5269
|
+
slot,
|
|
2822
5270
|
updateProperty,
|
|
2823
5271
|
useDesignerState,
|
|
2824
5272
|
validateCapabilities
|