@petrarca/sonnet-schema-designer 0.5.0 → 0.6.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/dist/index.d.ts +327 -26
- package/dist/index.js +3025 -606
- 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
|
}
|
|
@@ -586,7 +1312,7 @@ function validateCapabilities(input) {
|
|
|
586
1312
|
}
|
|
587
1313
|
|
|
588
1314
|
// src/model/scoping.ts
|
|
589
|
-
function
|
|
1315
|
+
function escapeToken2(token) {
|
|
590
1316
|
return token.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
591
1317
|
}
|
|
592
1318
|
function parentContainerPointer(schema, path) {
|
|
@@ -595,20 +1321,27 @@ function parentContainerPointer(schema, path) {
|
|
|
595
1321
|
let pointer = "/properties";
|
|
596
1322
|
for (let i = 0; i < path.length; i++) {
|
|
597
1323
|
if (!container) return void 0;
|
|
598
|
-
const
|
|
599
|
-
|
|
600
|
-
|
|
1324
|
+
const segment = path[i];
|
|
1325
|
+
if (segment.kind !== "prop") return void 0;
|
|
1326
|
+
const key = segment.key;
|
|
1327
|
+
const prop2 = container[key];
|
|
1328
|
+
if (!prop2) return void 0;
|
|
601
1329
|
if (i === path.length - 1) return pointer;
|
|
602
|
-
const next = childContainer(
|
|
1330
|
+
const next = childContainer(prop2);
|
|
603
1331
|
if (!next) return void 0;
|
|
604
|
-
pointer =
|
|
1332
|
+
pointer = prop2.type === "array" ? `${pointer}/${escapeToken2(key)}/items/properties` : `${pointer}/${escapeToken2(key)}/properties`;
|
|
605
1333
|
container = next;
|
|
606
1334
|
}
|
|
607
1335
|
return pointer;
|
|
608
1336
|
}
|
|
609
1337
|
function isPropertyEditable(schema, path, editablePaths) {
|
|
610
1338
|
if (editablePaths.length === 0) return true;
|
|
611
|
-
const
|
|
1339
|
+
const allPropPrefix = path.slice(
|
|
1340
|
+
0,
|
|
1341
|
+
path.findIndex((s) => s.kind !== "prop") + 1 || path.length
|
|
1342
|
+
);
|
|
1343
|
+
const effectivePath = allPropPrefix.length < path.length ? allPropPrefix : path;
|
|
1344
|
+
const parent = parentContainerPointer(schema, effectivePath);
|
|
612
1345
|
if (parent === void 0) return false;
|
|
613
1346
|
return editablePaths.includes(parent);
|
|
614
1347
|
}
|
|
@@ -623,18 +1356,89 @@ function ownChildContainerPointer(schema, path) {
|
|
|
623
1356
|
if (parent === void 0) return void 0;
|
|
624
1357
|
let container = schema.properties;
|
|
625
1358
|
for (let i = 0; i < path.length; i++) {
|
|
626
|
-
const
|
|
627
|
-
if (
|
|
1359
|
+
const segment = path[i];
|
|
1360
|
+
if (segment.kind !== "prop") return void 0;
|
|
1361
|
+
const prop2 = container?.[segment.key];
|
|
1362
|
+
if (!prop2) return void 0;
|
|
628
1363
|
if (i === path.length - 1) {
|
|
629
|
-
const seg =
|
|
630
|
-
return
|
|
1364
|
+
const seg = escapeToken2(segment.key);
|
|
1365
|
+
return prop2.type === "array" ? `${parent}/${seg}/items/properties` : `${parent}/${seg}/properties`;
|
|
631
1366
|
}
|
|
632
|
-
container = childContainer(
|
|
1367
|
+
container = childContainer(prop2);
|
|
633
1368
|
}
|
|
634
1369
|
return void 0;
|
|
635
1370
|
}
|
|
636
1371
|
|
|
637
1372
|
// src/hooks/useDesignerState.ts
|
|
1373
|
+
function pathStartsWith(p, prefix) {
|
|
1374
|
+
if (p.length < prefix.length) return false;
|
|
1375
|
+
return prefix.every((seg, i) => sameSegment(seg, p[i]));
|
|
1376
|
+
}
|
|
1377
|
+
function sharesParentPrefix(p, removed) {
|
|
1378
|
+
if (p.length < removed.length) return false;
|
|
1379
|
+
const parentLen = removed.length - 1;
|
|
1380
|
+
for (let i = 0; i < parentLen; i++) {
|
|
1381
|
+
if (!sameSegment(p[i], removed[i])) return false;
|
|
1382
|
+
}
|
|
1383
|
+
return true;
|
|
1384
|
+
}
|
|
1385
|
+
function sameIndexedArray(a, b) {
|
|
1386
|
+
if (a.kind !== b.kind) return false;
|
|
1387
|
+
if (a.kind === "branch" && b.kind === "branch") return true;
|
|
1388
|
+
if (a.kind === "condition" && b.kind === "condition")
|
|
1389
|
+
return a.combinator === b.combinator;
|
|
1390
|
+
return false;
|
|
1391
|
+
}
|
|
1392
|
+
function affectedSibling(prev, removed, kind) {
|
|
1393
|
+
const last = removed[removed.length - 1];
|
|
1394
|
+
if (!last || last.kind !== kind) return null;
|
|
1395
|
+
if (!sharesParentPrefix(prev, removed)) return null;
|
|
1396
|
+
const at = removed.length - 1;
|
|
1397
|
+
const seg = prev[at];
|
|
1398
|
+
if (!seg || !sameIndexedArray(seg, last) || !("index" in seg)) return null;
|
|
1399
|
+
return { at, seg };
|
|
1400
|
+
}
|
|
1401
|
+
function adjustIndexedSelection(prev, removed, kind) {
|
|
1402
|
+
if (!prev) return null;
|
|
1403
|
+
const hit = affectedSibling(prev, removed, kind);
|
|
1404
|
+
if (!hit) return prev;
|
|
1405
|
+
const removedIndex = removed[removed.length - 1].index;
|
|
1406
|
+
if (hit.seg.index === removedIndex) return null;
|
|
1407
|
+
if (hit.seg.index < removedIndex) return prev;
|
|
1408
|
+
const shifted = { ...hit.seg, index: hit.seg.index - 1 };
|
|
1409
|
+
return [...prev.slice(0, hit.at), shifted, ...prev.slice(hit.at + 1)];
|
|
1410
|
+
}
|
|
1411
|
+
function adjustBranchSelection(prev, removed) {
|
|
1412
|
+
return adjustIndexedSelection(prev, removed, "branch");
|
|
1413
|
+
}
|
|
1414
|
+
function countConditionals(arr) {
|
|
1415
|
+
return Array.isArray(arr) ? arr.filter((m) => !!m.if).length : 0;
|
|
1416
|
+
}
|
|
1417
|
+
function selectedConditionInCombinator(prev, objectPath, from) {
|
|
1418
|
+
if (!prev) return null;
|
|
1419
|
+
const at = objectPath.length;
|
|
1420
|
+
const seg = prev[at];
|
|
1421
|
+
if (!seg || seg.kind !== "condition" || seg.combinator !== from) return null;
|
|
1422
|
+
if (!samePath(prev.slice(0, at), objectPath)) return null;
|
|
1423
|
+
return { at, index: seg.index };
|
|
1424
|
+
}
|
|
1425
|
+
function remapConditionSelection(before, prev, objectPath, next) {
|
|
1426
|
+
const from = next === "allOf" ? "anyOf" : "allOf";
|
|
1427
|
+
const hit = selectedConditionInCombinator(prev, objectPath, from);
|
|
1428
|
+
if (!hit || !prev) return prev;
|
|
1429
|
+
const owner = objectPath.length === 0 ? { allOf: before.schema.allOf, anyOf: before.schema.anyOf } : getProperty(before.schema, objectPath);
|
|
1430
|
+
const sourceArr = owner?.[from];
|
|
1431
|
+
if (!Array.isArray(sourceArr)) return prev;
|
|
1432
|
+
const posAmongSource = countConditionals(sourceArr.slice(0, hit.index));
|
|
1433
|
+
const targetOffset = countConditionals(
|
|
1434
|
+
owner?.[next]
|
|
1435
|
+
);
|
|
1436
|
+
return [
|
|
1437
|
+
...prev.slice(0, hit.at),
|
|
1438
|
+
condition(targetOffset + posAmongSource, next),
|
|
1439
|
+
...prev.slice(hit.at + 1)
|
|
1440
|
+
];
|
|
1441
|
+
}
|
|
638
1442
|
var EMPTY_SCHEMA = {
|
|
639
1443
|
title: "",
|
|
640
1444
|
type: "object",
|
|
@@ -695,6 +1499,56 @@ function useDesignerState(args) {
|
|
|
695
1499
|
commit(res.model);
|
|
696
1500
|
setSelection(res.path);
|
|
697
1501
|
},
|
|
1502
|
+
addBranch(path) {
|
|
1503
|
+
if (!canEditProperty(path)) return;
|
|
1504
|
+
const res = addBranch(modelRef.current, path);
|
|
1505
|
+
commit(res.model);
|
|
1506
|
+
setSelection(res.path);
|
|
1507
|
+
},
|
|
1508
|
+
removeBranch(path) {
|
|
1509
|
+
if (!canEditProperty(path)) return;
|
|
1510
|
+
commit(removeBranch(modelRef.current, path));
|
|
1511
|
+
setSelection((prev) => adjustBranchSelection(prev, path));
|
|
1512
|
+
},
|
|
1513
|
+
moveBranch(path, direction) {
|
|
1514
|
+
if (!canEditProperty(path)) return;
|
|
1515
|
+
commit(moveBranch(modelRef.current, path, direction));
|
|
1516
|
+
},
|
|
1517
|
+
addConditional(objectPath, combinator = "allOf") {
|
|
1518
|
+
if (!canAddChild(objectPath)) return;
|
|
1519
|
+
const res = addConditional(modelRef.current, objectPath, combinator);
|
|
1520
|
+
commit(res.model);
|
|
1521
|
+
setSelection(res.path);
|
|
1522
|
+
},
|
|
1523
|
+
setConditionalBehavior(objectPath, combinator) {
|
|
1524
|
+
if (!canAddChild(objectPath)) return;
|
|
1525
|
+
const before = modelRef.current;
|
|
1526
|
+
commit(setConditionalBehavior(before, objectPath, combinator));
|
|
1527
|
+
setSelection(
|
|
1528
|
+
(prev) => remapConditionSelection(before, prev, objectPath, combinator)
|
|
1529
|
+
);
|
|
1530
|
+
},
|
|
1531
|
+
removeConditional(path) {
|
|
1532
|
+
if (!canAddChild(path.slice(0, -1))) return;
|
|
1533
|
+
commit(removeConditional(modelRef.current, path));
|
|
1534
|
+
setSelection((prev) => adjustIndexedSelection(prev, path, "condition"));
|
|
1535
|
+
},
|
|
1536
|
+
setCondition(path, ifSchema) {
|
|
1537
|
+
if (!canAddChild(path.slice(0, -1))) return;
|
|
1538
|
+
commit(setCondition(modelRef.current, path, ifSchema));
|
|
1539
|
+
},
|
|
1540
|
+
addElse(path) {
|
|
1541
|
+
if (!canAddChild(path.slice(0, -1))) return;
|
|
1542
|
+
commit(addElse(modelRef.current, path));
|
|
1543
|
+
},
|
|
1544
|
+
removeElse(path) {
|
|
1545
|
+
if (!canAddChild(path.slice(0, -1))) return;
|
|
1546
|
+
commit(removeElse(modelRef.current, path));
|
|
1547
|
+
const elsePath = [...path, slot("else")];
|
|
1548
|
+
setSelection(
|
|
1549
|
+
(prev) => prev && pathStartsWith(prev, elsePath) ? null : prev
|
|
1550
|
+
);
|
|
1551
|
+
},
|
|
698
1552
|
deleteProperty(path) {
|
|
699
1553
|
if (!canEditProperty(path)) return;
|
|
700
1554
|
commit(deleteProperty(modelRef.current, path));
|
|
@@ -718,9 +1572,48 @@ function useDesignerState(args) {
|
|
|
718
1572
|
if (!canEditProperty(path)) return;
|
|
719
1573
|
commit(updateProperty(modelRef.current, path, patch));
|
|
720
1574
|
},
|
|
721
|
-
changePropertyType(path,
|
|
1575
|
+
changePropertyType(path, next) {
|
|
722
1576
|
if (!canEditProperty(path)) return;
|
|
723
|
-
commit(changePropertyType(modelRef.current, path,
|
|
1577
|
+
commit(changePropertyType(modelRef.current, path, next));
|
|
1578
|
+
},
|
|
1579
|
+
setReference(path, name) {
|
|
1580
|
+
if (!canEditProperty(path)) return;
|
|
1581
|
+
commit(setReference(modelRef.current, path, name));
|
|
1582
|
+
},
|
|
1583
|
+
// Definitions are document-level, not path-scoped: gate on lock only.
|
|
1584
|
+
addDefinition(name) {
|
|
1585
|
+
if (capabilities.locked) return;
|
|
1586
|
+
const res = addDefinition(modelRef.current, name);
|
|
1587
|
+
commit(res.model);
|
|
1588
|
+
setSelection(res.path);
|
|
1589
|
+
},
|
|
1590
|
+
renameDefinition(oldName, newName) {
|
|
1591
|
+
if (capabilities.locked) return;
|
|
1592
|
+
const res = renameDefinition(modelRef.current, oldName, newName);
|
|
1593
|
+
commit(res.model);
|
|
1594
|
+
setSelection(
|
|
1595
|
+
(prev) => prev && samePath(prev, [def(oldName)]) ? res.path : prev
|
|
1596
|
+
);
|
|
1597
|
+
},
|
|
1598
|
+
removeDefinition(name) {
|
|
1599
|
+
if (capabilities.locked) return;
|
|
1600
|
+
commit(removeDefinition(modelRef.current, name));
|
|
1601
|
+
setSelection(
|
|
1602
|
+
(prev) => prev && pathStartsWith(prev, [def(name)]) ? null : prev
|
|
1603
|
+
);
|
|
1604
|
+
},
|
|
1605
|
+
// Replace the whole JSON Schema (from the editable JSON Schema tab).
|
|
1606
|
+
// The uiSchema is preserved. Gated by capabilities.locked only -- a
|
|
1607
|
+
// wholesale replace is not subject to per-path scoping.
|
|
1608
|
+
replaceSchema(schema) {
|
|
1609
|
+
if (capabilities.locked) return;
|
|
1610
|
+
commit({ schema, uiSchema: modelRef.current.uiSchema });
|
|
1611
|
+
setSelection(null);
|
|
1612
|
+
},
|
|
1613
|
+
// Replace the whole UI Schema map (from the editable UI Schema tab).
|
|
1614
|
+
replaceUiSchema(uiSchema) {
|
|
1615
|
+
if (readOnly) return;
|
|
1616
|
+
commit({ schema: modelRef.current.schema, uiSchema });
|
|
724
1617
|
},
|
|
725
1618
|
setEnum(path, choices) {
|
|
726
1619
|
if (!canEditProperty(path)) return;
|
|
@@ -757,18 +1650,67 @@ function useDesignerState(args) {
|
|
|
757
1650
|
if (readOnly) return;
|
|
758
1651
|
commit(setScopedLayoutOptions(modelRef.current, scopePath, options));
|
|
759
1652
|
},
|
|
1653
|
+
setScopedLayoutGroups(scopePath, groups) {
|
|
1654
|
+
if (readOnly) return;
|
|
1655
|
+
commit(setScopedLayoutGroups(modelRef.current, scopePath, groups));
|
|
1656
|
+
},
|
|
1657
|
+
setScopedGroupRows(scopePath, groupIndex, rows) {
|
|
1658
|
+
if (readOnly) return;
|
|
1659
|
+
commit(
|
|
1660
|
+
setScopedGroupRows(modelRef.current, scopePath, groupIndex, rows)
|
|
1661
|
+
);
|
|
1662
|
+
},
|
|
1663
|
+
setScopedLayoutMode(scopePath, mode) {
|
|
1664
|
+
if (readOnly) return;
|
|
1665
|
+
commit(setScopedLayoutMode(modelRef.current, scopePath, mode));
|
|
1666
|
+
},
|
|
1667
|
+
addLayoutGroup(scopePath, label) {
|
|
1668
|
+
if (readOnly) return;
|
|
1669
|
+
commit(addLayoutGroup(modelRef.current, scopePath, label));
|
|
1670
|
+
},
|
|
1671
|
+
renameLayoutGroup(scopePath, groupIndex, label) {
|
|
1672
|
+
if (readOnly) return;
|
|
1673
|
+
commit(
|
|
1674
|
+
renameLayoutGroup(modelRef.current, scopePath, groupIndex, label)
|
|
1675
|
+
);
|
|
1676
|
+
},
|
|
1677
|
+
removeLayoutGroup(scopePath, groupIndex) {
|
|
1678
|
+
if (readOnly) return;
|
|
1679
|
+
commit(removeLayoutGroup(modelRef.current, scopePath, groupIndex));
|
|
1680
|
+
},
|
|
1681
|
+
moveLayoutGroup(scopePath, groupIndex, direction) {
|
|
1682
|
+
if (readOnly) return;
|
|
1683
|
+
commit(
|
|
1684
|
+
moveLayoutGroup(modelRef.current, scopePath, groupIndex, direction)
|
|
1685
|
+
);
|
|
1686
|
+
},
|
|
1687
|
+
setLayoutGroupRule(scopePath, groupIndex, rule) {
|
|
1688
|
+
if (readOnly) return;
|
|
1689
|
+
commit(
|
|
1690
|
+
setLayoutGroupRule(modelRef.current, scopePath, groupIndex, rule)
|
|
1691
|
+
);
|
|
1692
|
+
},
|
|
1693
|
+
setScopedLayoutVariant(scopePath, variant) {
|
|
1694
|
+
if (readOnly) return;
|
|
1695
|
+
commit(setScopedLayoutVariant(modelRef.current, scopePath, variant));
|
|
1696
|
+
},
|
|
1697
|
+
placeFieldInGroup(scopePath, key, target) {
|
|
1698
|
+
if (readOnly) return;
|
|
1699
|
+
commit(placeFieldInGroup(modelRef.current, scopePath, key, target));
|
|
1700
|
+
},
|
|
760
1701
|
setGlobalOptions(options) {
|
|
761
1702
|
if (readOnly) return;
|
|
762
1703
|
commit(setGlobalOptions(modelRef.current, options));
|
|
763
1704
|
}
|
|
764
1705
|
}),
|
|
765
|
-
[commit, readOnly, canEditProperty, canAddChild]
|
|
1706
|
+
[commit, readOnly, capabilities.locked, canEditProperty, canAddChild]
|
|
766
1707
|
);
|
|
767
1708
|
return {
|
|
768
1709
|
schema: model.schema,
|
|
769
1710
|
uiSchema: model.uiSchema,
|
|
770
1711
|
selection,
|
|
771
1712
|
readOnly,
|
|
1713
|
+
refResolver: args.refResolver,
|
|
772
1714
|
capabilities,
|
|
773
1715
|
canEditProperty,
|
|
774
1716
|
canAddChild,
|
|
@@ -778,7 +1720,7 @@ function useDesignerState(args) {
|
|
|
778
1720
|
}
|
|
779
1721
|
|
|
780
1722
|
// src/SchemaDesigner.tsx
|
|
781
|
-
import { useEffect as
|
|
1723
|
+
import { useEffect as useEffect6, useState as useState9 } from "react";
|
|
782
1724
|
import { ResizeHandle, IconButton as IconButton5 } from "@petrarca/sonnet-ui";
|
|
783
1725
|
import { useResizablePanel } from "@petrarca/sonnet-core/hooks";
|
|
784
1726
|
import {
|
|
@@ -951,7 +1893,7 @@ function FormSettingsSheet({
|
|
|
951
1893
|
}
|
|
952
1894
|
|
|
953
1895
|
// src/tree/PropertyTree.tsx
|
|
954
|
-
import { useEffect, useRef as useRef2, useState as useState2 } from "react";
|
|
1896
|
+
import { useEffect, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
|
|
955
1897
|
import { cn } from "@petrarca/sonnet-core";
|
|
956
1898
|
import { IconButton, Input } from "@petrarca/sonnet-ui";
|
|
957
1899
|
import {
|
|
@@ -960,30 +1902,90 @@ import {
|
|
|
960
1902
|
ChevronUp,
|
|
961
1903
|
ChevronDown,
|
|
962
1904
|
PanelLeftClose,
|
|
963
|
-
Lock
|
|
1905
|
+
Lock,
|
|
1906
|
+
GitBranch,
|
|
1907
|
+
SplitSquareHorizontal,
|
|
1908
|
+
Eye,
|
|
1909
|
+
Braces,
|
|
1910
|
+
Brackets,
|
|
1911
|
+
GitFork,
|
|
1912
|
+
CornerDownRight,
|
|
1913
|
+
Type,
|
|
1914
|
+
List,
|
|
1915
|
+
ToggleLeft,
|
|
1916
|
+
Hash,
|
|
1917
|
+
Link2,
|
|
1918
|
+
Puzzle,
|
|
1919
|
+
ChevronRight
|
|
964
1920
|
} from "lucide-react";
|
|
965
1921
|
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1922
|
+
function buildInUseDefs(schema, names) {
|
|
1923
|
+
return new Set(names.filter((n) => definitionUsageCount(schema, n) > 0));
|
|
1924
|
+
}
|
|
966
1925
|
function PropertyTree({
|
|
967
1926
|
state,
|
|
968
1927
|
onCollapse
|
|
969
1928
|
}) {
|
|
970
|
-
const nodes =
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
1929
|
+
const nodes = useMemo2(
|
|
1930
|
+
() => buildPropertyTree(state.schema, state.refResolver),
|
|
1931
|
+
[state.schema, state.refResolver]
|
|
1932
|
+
);
|
|
1933
|
+
const propertyNodes = useMemo2(
|
|
1934
|
+
() => nodes.filter((n) => n.kind !== "definition"),
|
|
1935
|
+
[nodes]
|
|
1936
|
+
);
|
|
1937
|
+
const definitionNodes = useMemo2(
|
|
1938
|
+
() => nodes.filter((n) => n.kind === "definition"),
|
|
1939
|
+
[nodes]
|
|
1940
|
+
);
|
|
1941
|
+
const canEditDefs = !state.capabilities.locked;
|
|
1942
|
+
const [propsOpen, setPropsOpen] = useState2(true);
|
|
1943
|
+
return /* @__PURE__ */ jsxs2("div", { className: "flex h-full flex-col min-h-0", children: [
|
|
1944
|
+
/* @__PURE__ */ jsxs2("div", { className: "mb-2 flex items-center gap-2 shrink-0", children: [
|
|
1945
|
+
/* @__PURE__ */ jsxs2(
|
|
1946
|
+
"button",
|
|
1947
|
+
{
|
|
1948
|
+
type: "button",
|
|
1949
|
+
className: "flex items-center gap-1 heading-meta",
|
|
1950
|
+
onClick: () => setPropsOpen((v) => !v),
|
|
1951
|
+
children: [
|
|
1952
|
+
propsOpen ? /* @__PURE__ */ jsx2(ChevronDown, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx2(ChevronRight, { className: "h-3 w-3" }),
|
|
1953
|
+
"Properties",
|
|
1954
|
+
propertyNodes.length > 0 && /* @__PURE__ */ jsxs2("span", { className: "text-muted-foreground/60", children: [
|
|
1955
|
+
"(",
|
|
1956
|
+
propertyNodes.length,
|
|
1957
|
+
")"
|
|
1958
|
+
] })
|
|
1959
|
+
]
|
|
1960
|
+
}
|
|
1961
|
+
),
|
|
974
1962
|
/* @__PURE__ */ jsxs2("span", { className: "ml-auto flex gap-1", children: [
|
|
975
1963
|
state.canAddChild([]) && /* @__PURE__ */ jsx2(
|
|
976
1964
|
IconButton,
|
|
977
1965
|
{
|
|
978
1966
|
label: "Add property",
|
|
979
|
-
onClick: () =>
|
|
1967
|
+
onClick: () => {
|
|
1968
|
+
setPropsOpen(true);
|
|
1969
|
+
state.addProperty([], "scalar");
|
|
1970
|
+
},
|
|
980
1971
|
children: /* @__PURE__ */ jsx2(Plus, {})
|
|
981
1972
|
}
|
|
982
1973
|
),
|
|
1974
|
+
state.canAddChild([]) && /* @__PURE__ */ jsx2(
|
|
1975
|
+
IconButton,
|
|
1976
|
+
{
|
|
1977
|
+
label: "Add condition",
|
|
1978
|
+
onClick: () => {
|
|
1979
|
+
setPropsOpen(true);
|
|
1980
|
+
state.addConditional([]);
|
|
1981
|
+
},
|
|
1982
|
+
children: /* @__PURE__ */ jsx2(GitBranch, {})
|
|
1983
|
+
}
|
|
1984
|
+
),
|
|
983
1985
|
onCollapse && /* @__PURE__ */ jsx2(IconButton, { label: "Collapse properties", onClick: onCollapse, children: /* @__PURE__ */ jsx2(PanelLeftClose, {}) })
|
|
984
1986
|
] })
|
|
985
1987
|
] }),
|
|
986
|
-
|
|
1988
|
+
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
1989
|
PropertyRow,
|
|
988
1990
|
{
|
|
989
1991
|
node,
|
|
@@ -991,7 +1993,72 @@ function PropertyTree({
|
|
|
991
1993
|
depth: 0
|
|
992
1994
|
},
|
|
993
1995
|
pathKey(node.path)
|
|
994
|
-
)) })
|
|
1996
|
+
)) })),
|
|
1997
|
+
/* @__PURE__ */ jsx2(
|
|
1998
|
+
DefinitionsRegion,
|
|
1999
|
+
{
|
|
2000
|
+
nodes: definitionNodes,
|
|
2001
|
+
state,
|
|
2002
|
+
canEdit: canEditDefs
|
|
2003
|
+
}
|
|
2004
|
+
)
|
|
2005
|
+
] });
|
|
2006
|
+
}
|
|
2007
|
+
function DefinitionsRegion({
|
|
2008
|
+
nodes,
|
|
2009
|
+
state,
|
|
2010
|
+
canEdit
|
|
2011
|
+
}) {
|
|
2012
|
+
const [open, setOpen] = useState2(false);
|
|
2013
|
+
const inUseDefs = useMemo2(
|
|
2014
|
+
() => buildInUseDefs(
|
|
2015
|
+
state.schema,
|
|
2016
|
+
nodes.map((n) => n.key)
|
|
2017
|
+
),
|
|
2018
|
+
[state.schema, nodes]
|
|
2019
|
+
);
|
|
2020
|
+
if (nodes.length === 0 && !canEdit) return null;
|
|
2021
|
+
return /* @__PURE__ */ jsxs2("div", { className: "mt-3 shrink-0", children: [
|
|
2022
|
+
/* @__PURE__ */ jsxs2("div", { className: "mb-2 flex items-center gap-2", children: [
|
|
2023
|
+
/* @__PURE__ */ jsxs2(
|
|
2024
|
+
"button",
|
|
2025
|
+
{
|
|
2026
|
+
type: "button",
|
|
2027
|
+
className: "flex items-center gap-1 heading-meta",
|
|
2028
|
+
onClick: () => setOpen((v) => !v),
|
|
2029
|
+
children: [
|
|
2030
|
+
open ? /* @__PURE__ */ jsx2(ChevronDown, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx2(ChevronRight, { className: "h-3 w-3" }),
|
|
2031
|
+
"Definitions",
|
|
2032
|
+
nodes.length > 0 && /* @__PURE__ */ jsxs2("span", { className: "text-muted-foreground/60", children: [
|
|
2033
|
+
"(",
|
|
2034
|
+
nodes.length,
|
|
2035
|
+
")"
|
|
2036
|
+
] })
|
|
2037
|
+
]
|
|
2038
|
+
}
|
|
2039
|
+
),
|
|
2040
|
+
canEdit && /* @__PURE__ */ jsx2("span", { className: "ml-auto flex gap-1", children: /* @__PURE__ */ jsx2(
|
|
2041
|
+
IconButton,
|
|
2042
|
+
{
|
|
2043
|
+
label: "Add definition",
|
|
2044
|
+
onClick: () => {
|
|
2045
|
+
setOpen(true);
|
|
2046
|
+
state.addDefinition();
|
|
2047
|
+
},
|
|
2048
|
+
children: /* @__PURE__ */ jsx2(Plus, {})
|
|
2049
|
+
}
|
|
2050
|
+
) })
|
|
2051
|
+
] }),
|
|
2052
|
+
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(
|
|
2053
|
+
PropertyRow,
|
|
2054
|
+
{
|
|
2055
|
+
node,
|
|
2056
|
+
state,
|
|
2057
|
+
depth: 0,
|
|
2058
|
+
inUseDefs
|
|
2059
|
+
},
|
|
2060
|
+
pathKey(node.path)
|
|
2061
|
+
)) }))
|
|
995
2062
|
] });
|
|
996
2063
|
}
|
|
997
2064
|
function NameEditor({
|
|
@@ -1031,23 +2098,82 @@ function NameEditor({
|
|
|
1031
2098
|
}
|
|
1032
2099
|
);
|
|
1033
2100
|
}
|
|
2101
|
+
var KIND_ICONS = {
|
|
2102
|
+
reference: { Icon: Link2, label: "reference" },
|
|
2103
|
+
definition: { Icon: Puzzle, label: "definition" },
|
|
2104
|
+
conditional: { Icon: GitBranch, label: "condition" },
|
|
2105
|
+
object: { Icon: Braces, label: "object" },
|
|
2106
|
+
array: { Icon: Brackets, label: "array" },
|
|
2107
|
+
oneOf: { Icon: GitFork, label: "one of" },
|
|
2108
|
+
enum: { Icon: List, label: "choice" }
|
|
2109
|
+
};
|
|
2110
|
+
function kindIconFor(node) {
|
|
2111
|
+
const last = node.path[node.path.length - 1];
|
|
2112
|
+
if (last?.kind === "slot") return { Icon: CornerDownRight, label: "branch" };
|
|
2113
|
+
if (node.slotKind === "branch") return { Icon: GitFork, label: "variant" };
|
|
2114
|
+
return KIND_ICONS[node.kind] ?? scalarIcon(node.property.type);
|
|
2115
|
+
}
|
|
2116
|
+
function scalarIcon(type) {
|
|
2117
|
+
if (type === "number" || type === "integer")
|
|
2118
|
+
return { Icon: Hash, label: "number" };
|
|
2119
|
+
if (type === "boolean") return { Icon: ToggleLeft, label: "boolean" };
|
|
2120
|
+
return { Icon: Type, label: "text" };
|
|
2121
|
+
}
|
|
2122
|
+
function KindIcon({ node }) {
|
|
2123
|
+
const { Icon, label } = kindIconFor(node);
|
|
2124
|
+
return /* @__PURE__ */ jsx2(
|
|
2125
|
+
Icon,
|
|
2126
|
+
{
|
|
2127
|
+
className: "h-3.5 w-3.5 shrink-0 text-muted-foreground/60",
|
|
2128
|
+
"aria-label": label
|
|
2129
|
+
}
|
|
2130
|
+
);
|
|
2131
|
+
}
|
|
2132
|
+
function Markers({ node }) {
|
|
2133
|
+
const rule = node.property["x-ui-rule"];
|
|
2134
|
+
if (!node.required && !rule) return null;
|
|
2135
|
+
const scopeField = rule?.condition.scope.replace(/^\//, "");
|
|
2136
|
+
const ruleTip = rule ? `${rule.effect} when ${scopeField || rule.condition.scope}` : "";
|
|
2137
|
+
return /* @__PURE__ */ jsxs2("span", { className: "shrink-0 flex items-center gap-1 group-hover/row:hidden", children: [
|
|
2138
|
+
node.required && /* @__PURE__ */ jsx2(
|
|
2139
|
+
"span",
|
|
2140
|
+
{
|
|
2141
|
+
className: "text-destructive text-xs leading-none",
|
|
2142
|
+
title: "required",
|
|
2143
|
+
"aria-label": "required",
|
|
2144
|
+
children: "*"
|
|
2145
|
+
}
|
|
2146
|
+
),
|
|
2147
|
+
rule && /* @__PURE__ */ jsx2("span", { title: ruleTip, "aria-label": ruleTip, className: "flex", children: /* @__PURE__ */ jsx2(Eye, { className: "h-3 w-3 text-primary" }) })
|
|
2148
|
+
] });
|
|
2149
|
+
}
|
|
2150
|
+
function canAddChildHere(node, state, isConditional) {
|
|
2151
|
+
if (!acceptsChildren(node)) return false;
|
|
2152
|
+
return node.kind === "oneOf" || isConditional ? state.canEditProperty(node.path) : state.canAddChild(node.path);
|
|
2153
|
+
}
|
|
2154
|
+
function isRuleStyled(node) {
|
|
2155
|
+
const last = node.path[node.path.length - 1];
|
|
2156
|
+
return node.kind === "conditional" || last?.kind === "slot";
|
|
2157
|
+
}
|
|
1034
2158
|
function RowLabel({
|
|
1035
2159
|
node,
|
|
1036
2160
|
editable,
|
|
1037
2161
|
onStartRename
|
|
1038
2162
|
}) {
|
|
1039
2163
|
const title = editable ? `${node.key}${node.required ? " (required)" : ""} -- double-click to rename` : `${node.key} -- fixed (not editable)`;
|
|
2164
|
+
const ruleStyled = isRuleStyled(node);
|
|
1040
2165
|
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
1041
|
-
/* @__PURE__ */
|
|
2166
|
+
/* @__PURE__ */ jsx2(KindIcon, { node }),
|
|
2167
|
+
/* @__PURE__ */ jsx2(
|
|
1042
2168
|
"span",
|
|
1043
2169
|
{
|
|
1044
|
-
className:
|
|
2170
|
+
className: cn(
|
|
2171
|
+
"flex-1 min-w-0 truncate text-sm",
|
|
2172
|
+
ruleStyled ? "font-mono text-xs uppercase tracking-wide text-muted-foreground" : "font-medium"
|
|
2173
|
+
),
|
|
1045
2174
|
onDoubleClick: onStartRename,
|
|
1046
2175
|
title,
|
|
1047
|
-
children:
|
|
1048
|
-
node.key,
|
|
1049
|
-
node.required && /* @__PURE__ */ jsx2("span", { className: "ml-0.5 text-destructive", "aria-label": "required", children: "*" })
|
|
1050
|
-
]
|
|
2176
|
+
children: node.key
|
|
1051
2177
|
}
|
|
1052
2178
|
),
|
|
1053
2179
|
!editable && /* @__PURE__ */ jsx2(
|
|
@@ -1057,89 +2183,192 @@ function RowLabel({
|
|
|
1057
2183
|
"aria-label": "fixed"
|
|
1058
2184
|
}
|
|
1059
2185
|
),
|
|
2186
|
+
/* @__PURE__ */ jsx2(Markers, { node }),
|
|
1060
2187
|
/* @__PURE__ */ jsx2(
|
|
1061
2188
|
"span",
|
|
1062
2189
|
{
|
|
1063
|
-
className: "shrink-0 max-w-[
|
|
2190
|
+
className: "shrink-0 max-w-[40%] truncate font-mono text-[11px] text-muted-foreground/70 group-hover/row:hidden",
|
|
1064
2191
|
title: node.typeLabel,
|
|
1065
2192
|
children: node.typeLabel
|
|
1066
2193
|
}
|
|
1067
2194
|
)
|
|
1068
2195
|
] });
|
|
1069
2196
|
}
|
|
1070
|
-
function
|
|
2197
|
+
function isConditionalMember2(node) {
|
|
2198
|
+
return node.kind === "conditional";
|
|
2199
|
+
}
|
|
2200
|
+
function isConditionalSlot(node) {
|
|
2201
|
+
const last = node.path[node.path.length - 1];
|
|
2202
|
+
return node.slotKind === "conditional" && last?.kind === "slot";
|
|
2203
|
+
}
|
|
2204
|
+
function acceptsChildren(node) {
|
|
2205
|
+
return node.kind === "object" || node.kind === "array" || node.kind === "oneOf" || node.kind === "definition" || node.slotKind === "branch" || isConditionalSlot(node);
|
|
2206
|
+
}
|
|
2207
|
+
function acceptsConditions(node) {
|
|
2208
|
+
return node.kind === "object" || isConditionalSlot(node);
|
|
2209
|
+
}
|
|
2210
|
+
function ActionButton({
|
|
2211
|
+
label,
|
|
2212
|
+
tone,
|
|
2213
|
+
disabled,
|
|
2214
|
+
onAct,
|
|
2215
|
+
children
|
|
2216
|
+
}) {
|
|
2217
|
+
return /* @__PURE__ */ jsx2(
|
|
2218
|
+
IconButton,
|
|
2219
|
+
{
|
|
2220
|
+
label,
|
|
2221
|
+
tone,
|
|
2222
|
+
disabled,
|
|
2223
|
+
revealOnHover: true,
|
|
2224
|
+
onClick: (e) => {
|
|
2225
|
+
e.stopPropagation();
|
|
2226
|
+
onAct();
|
|
2227
|
+
},
|
|
2228
|
+
children
|
|
2229
|
+
}
|
|
2230
|
+
);
|
|
2231
|
+
}
|
|
2232
|
+
function MoveActions({
|
|
2233
|
+
node,
|
|
2234
|
+
state
|
|
2235
|
+
}) {
|
|
2236
|
+
const isBranch = node.slotKind === "branch";
|
|
2237
|
+
const move = (dir) => isBranch ? state.moveBranch(node.path, dir) : state.moveProperty(node.path, dir);
|
|
2238
|
+
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
2239
|
+
/* @__PURE__ */ jsx2(ActionButton, { label: "Move up", onAct: () => move(-1), children: /* @__PURE__ */ jsx2(ChevronUp, {}) }),
|
|
2240
|
+
/* @__PURE__ */ jsx2(ActionButton, { label: "Move down", onAct: () => move(1), children: /* @__PURE__ */ jsx2(ChevronDown, {}) })
|
|
2241
|
+
] });
|
|
2242
|
+
}
|
|
2243
|
+
function AddActions({
|
|
1071
2244
|
node,
|
|
1072
2245
|
state,
|
|
1073
2246
|
editable,
|
|
1074
2247
|
canAddHere
|
|
1075
2248
|
}) {
|
|
2249
|
+
const isOneOf3 = node.kind === "oneOf";
|
|
2250
|
+
const addChild = () => isOneOf3 ? state.addBranch(node.path) : state.addProperty(node.path, "scalar");
|
|
2251
|
+
const condMember = isConditionalMember2(node) && editable;
|
|
2252
|
+
const hasElse = !!node.property.else;
|
|
1076
2253
|
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
2254
|
canAddHere && /* @__PURE__ */ jsx2(
|
|
1104
|
-
|
|
2255
|
+
ActionButton,
|
|
1105
2256
|
{
|
|
1106
|
-
label: "Add child property",
|
|
1107
|
-
|
|
1108
|
-
onClick: (e) => {
|
|
1109
|
-
e.stopPropagation();
|
|
1110
|
-
state.addProperty(node.path, "scalar");
|
|
1111
|
-
},
|
|
2257
|
+
label: isOneOf3 ? "Add branch" : "Add child property",
|
|
2258
|
+
onAct: addChild,
|
|
1112
2259
|
children: /* @__PURE__ */ jsx2(Plus, {})
|
|
1113
2260
|
}
|
|
1114
2261
|
),
|
|
1115
|
-
editable && /* @__PURE__ */ jsx2(
|
|
1116
|
-
|
|
2262
|
+
editable && acceptsConditions(node) && /* @__PURE__ */ jsx2(
|
|
2263
|
+
ActionButton,
|
|
1117
2264
|
{
|
|
1118
|
-
label: "
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
onClick: (e) => {
|
|
1122
|
-
e.stopPropagation();
|
|
1123
|
-
state.deleteProperty(node.path);
|
|
1124
|
-
},
|
|
1125
|
-
children: /* @__PURE__ */ jsx2(Trash2, {})
|
|
2265
|
+
label: "Add condition",
|
|
2266
|
+
onAct: () => state.addConditional(node.path),
|
|
2267
|
+
children: /* @__PURE__ */ jsx2(GitBranch, {})
|
|
1126
2268
|
}
|
|
1127
|
-
)
|
|
2269
|
+
),
|
|
2270
|
+
condMember && !hasElse && /* @__PURE__ */ jsx2(ActionButton, { label: "Add else", onAct: () => state.addElse(node.path), children: /* @__PURE__ */ jsx2(SplitSquareHorizontal, {}) })
|
|
2271
|
+
] });
|
|
2272
|
+
}
|
|
2273
|
+
function lastSegment(node) {
|
|
2274
|
+
return node.path[node.path.length - 1];
|
|
2275
|
+
}
|
|
2276
|
+
function removeDescriptor(node, state, inUseDefs) {
|
|
2277
|
+
const last = lastSegment(node);
|
|
2278
|
+
const slotName = last?.kind === "slot" ? last.slot : void 0;
|
|
2279
|
+
if (slotName === "then") return null;
|
|
2280
|
+
if (node.slotKind === "branch") {
|
|
2281
|
+
return simpleRemove("Remove branch", () => state.removeBranch(node.path));
|
|
2282
|
+
}
|
|
2283
|
+
if (isConditionalMember2(node)) {
|
|
2284
|
+
return simpleRemove(
|
|
2285
|
+
"Remove condition",
|
|
2286
|
+
() => state.removeConditional(node.path)
|
|
2287
|
+
);
|
|
2288
|
+
}
|
|
2289
|
+
if (node.kind === "definition") {
|
|
2290
|
+
const inUse = inUseDefs.has(node.key);
|
|
2291
|
+
return {
|
|
2292
|
+
label: inUse ? "In use -- remove references first" : "Delete definition",
|
|
2293
|
+
disabled: inUse,
|
|
2294
|
+
remove: () => state.removeDefinition(node.key)
|
|
2295
|
+
};
|
|
2296
|
+
}
|
|
2297
|
+
if (slotName === "else") {
|
|
2298
|
+
return simpleRemove(
|
|
2299
|
+
"Remove else",
|
|
2300
|
+
() => state.removeElse(node.path.slice(0, -1))
|
|
2301
|
+
);
|
|
2302
|
+
}
|
|
2303
|
+
return simpleRemove("Delete property", () => state.deleteProperty(node.path));
|
|
2304
|
+
}
|
|
2305
|
+
function simpleRemove(label, remove) {
|
|
2306
|
+
return { label, disabled: false, remove };
|
|
2307
|
+
}
|
|
2308
|
+
function RemoveAction({
|
|
2309
|
+
node,
|
|
2310
|
+
state,
|
|
2311
|
+
inUseDefs
|
|
2312
|
+
}) {
|
|
2313
|
+
const desc = removeDescriptor(node, state, inUseDefs);
|
|
2314
|
+
if (!desc) return null;
|
|
2315
|
+
return /* @__PURE__ */ jsx2(
|
|
2316
|
+
ActionButton,
|
|
2317
|
+
{
|
|
2318
|
+
label: desc.label,
|
|
2319
|
+
tone: "destructive",
|
|
2320
|
+
disabled: desc.disabled,
|
|
2321
|
+
onAct: desc.remove,
|
|
2322
|
+
children: /* @__PURE__ */ jsx2(Trash2, {})
|
|
2323
|
+
}
|
|
2324
|
+
);
|
|
2325
|
+
}
|
|
2326
|
+
var EMPTY_SET = /* @__PURE__ */ new Set();
|
|
2327
|
+
function RowActions({
|
|
2328
|
+
node,
|
|
2329
|
+
state,
|
|
2330
|
+
editable,
|
|
2331
|
+
canAddHere,
|
|
2332
|
+
inUseDefs = EMPTY_SET
|
|
2333
|
+
}) {
|
|
2334
|
+
const reorderable = editable && node.slotKind !== "conditional" && node.slotKind !== "definition";
|
|
2335
|
+
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
2336
|
+
reorderable && /* @__PURE__ */ jsx2(MoveActions, { node, state }),
|
|
2337
|
+
/* @__PURE__ */ jsx2(
|
|
2338
|
+
AddActions,
|
|
2339
|
+
{
|
|
2340
|
+
node,
|
|
2341
|
+
state,
|
|
2342
|
+
editable,
|
|
2343
|
+
canAddHere
|
|
2344
|
+
}
|
|
2345
|
+
),
|
|
2346
|
+
editable && /* @__PURE__ */ jsx2(RemoveAction, { node, state, inUseDefs })
|
|
1128
2347
|
] });
|
|
1129
2348
|
}
|
|
1130
2349
|
function PropertyRow({
|
|
1131
2350
|
node,
|
|
1132
2351
|
state,
|
|
1133
|
-
depth
|
|
2352
|
+
depth,
|
|
2353
|
+
inUseDefs = EMPTY_SET
|
|
1134
2354
|
}) {
|
|
1135
2355
|
const selected = samePath(state.selection, node.path);
|
|
1136
2356
|
const [editing, setEditing] = useState2(false);
|
|
1137
|
-
const editable = state.canEditProperty(node.path);
|
|
1138
|
-
const
|
|
1139
|
-
const
|
|
2357
|
+
const editable = !node.readOnly && state.canEditProperty(node.path);
|
|
2358
|
+
const isBranch = node.slotKind === "branch";
|
|
2359
|
+
const isConditional = node.slotKind === "conditional";
|
|
2360
|
+
const isDefinition = node.kind === "definition";
|
|
2361
|
+
const editableName = isDefinition ? !state.capabilities.locked : editable;
|
|
2362
|
+
const canAddHere = canAddChildHere(node, state, isConditional);
|
|
2363
|
+
const showActions = editableName || canAddHere;
|
|
1140
2364
|
const startRename = (e) => {
|
|
1141
2365
|
e.stopPropagation();
|
|
1142
|
-
if (
|
|
2366
|
+
if (editableName && !isBranch && !isConditional) setEditing(true);
|
|
2367
|
+
};
|
|
2368
|
+
const commitRename = (next) => {
|
|
2369
|
+
setEditing(false);
|
|
2370
|
+
if (isDefinition) state.renameDefinition(node.key, next);
|
|
2371
|
+
else state.renameProperty(node.path, next);
|
|
1143
2372
|
};
|
|
1144
2373
|
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
1145
2374
|
/* @__PURE__ */ jsxs2(
|
|
@@ -1156,17 +2385,14 @@ function PropertyRow({
|
|
|
1156
2385
|
NameEditor,
|
|
1157
2386
|
{
|
|
1158
2387
|
initial: node.key,
|
|
1159
|
-
onCommit:
|
|
1160
|
-
setEditing(false);
|
|
1161
|
-
state.renameProperty(node.path, next);
|
|
1162
|
-
},
|
|
2388
|
+
onCommit: commitRename,
|
|
1163
2389
|
onCancel: () => setEditing(false)
|
|
1164
2390
|
}
|
|
1165
2391
|
) : /* @__PURE__ */ jsx2(
|
|
1166
2392
|
RowLabel,
|
|
1167
2393
|
{
|
|
1168
2394
|
node,
|
|
1169
|
-
editable,
|
|
2395
|
+
editable: editableName,
|
|
1170
2396
|
onStartRename: startRename
|
|
1171
2397
|
}
|
|
1172
2398
|
),
|
|
@@ -1187,8 +2413,9 @@ function PropertyRow({
|
|
|
1187
2413
|
{
|
|
1188
2414
|
node,
|
|
1189
2415
|
state,
|
|
1190
|
-
editable,
|
|
1191
|
-
canAddHere
|
|
2416
|
+
editable: editableName,
|
|
2417
|
+
canAddHere,
|
|
2418
|
+
inUseDefs
|
|
1192
2419
|
}
|
|
1193
2420
|
)
|
|
1194
2421
|
}
|
|
@@ -1201,7 +2428,8 @@ function PropertyRow({
|
|
|
1201
2428
|
{
|
|
1202
2429
|
node: child,
|
|
1203
2430
|
state,
|
|
1204
|
-
depth: depth + 1
|
|
2431
|
+
depth: depth + 1,
|
|
2432
|
+
inUseDefs
|
|
1205
2433
|
},
|
|
1206
2434
|
pathKey(child.path)
|
|
1207
2435
|
))
|
|
@@ -1209,29 +2437,27 @@ function PropertyRow({
|
|
|
1209
2437
|
}
|
|
1210
2438
|
|
|
1211
2439
|
// src/preview/PreviewPane.tsx
|
|
1212
|
-
import { useMemo as
|
|
1213
|
-
import { Check, Copy } from "lucide-react";
|
|
2440
|
+
import { useMemo as useMemo7, useState as useState6 } from "react";
|
|
1214
2441
|
import {
|
|
1215
|
-
Button as Button3,
|
|
1216
2442
|
Tabs,
|
|
1217
2443
|
TabsList,
|
|
1218
2444
|
TabsTrigger,
|
|
1219
2445
|
TabsContent,
|
|
1220
2446
|
TabsSeparator
|
|
1221
2447
|
} from "@petrarca/sonnet-ui";
|
|
1222
|
-
import { JsonEditor as JsonEditor2 } from "@petrarca/sonnet-ui/json-editor";
|
|
1223
2448
|
import { JsonSchemaFormRenderer as JsonSchemaFormRenderer3 } from "@petrarca/sonnet-forms";
|
|
1224
|
-
import {
|
|
1225
|
-
|
|
2449
|
+
import {
|
|
2450
|
+
extractSchemaDefaults
|
|
2451
|
+
} from "@petrarca/sonnet-core/schema";
|
|
1226
2452
|
|
|
1227
2453
|
// src/preview/TablePreview.tsx
|
|
1228
|
-
import { useMemo as
|
|
2454
|
+
import { useMemo as useMemo3 } from "react";
|
|
1229
2455
|
import { EntityTable } from "@petrarca/sonnet-ui";
|
|
1230
2456
|
import { toPageResult } from "@petrarca/sonnet-core";
|
|
1231
2457
|
|
|
1232
2458
|
// src/preview/sampleRows.ts
|
|
1233
|
-
function sampleString(
|
|
1234
|
-
switch (
|
|
2459
|
+
function sampleString(prop2, key, i) {
|
|
2460
|
+
switch (prop2.format) {
|
|
1235
2461
|
case "email":
|
|
1236
2462
|
return `user${i + 1}@example.com`;
|
|
1237
2463
|
case "date":
|
|
@@ -1251,13 +2477,13 @@ function titleCase(key) {
|
|
|
1251
2477
|
const s = key.replace(/[_-]+/g, " ").trim();
|
|
1252
2478
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
1253
2479
|
}
|
|
1254
|
-
function sampleValue(
|
|
1255
|
-
if (Array.isArray(
|
|
1256
|
-
return
|
|
2480
|
+
function sampleValue(prop2, key, i) {
|
|
2481
|
+
if (Array.isArray(prop2.enum) && prop2.enum.length > 0) {
|
|
2482
|
+
return prop2.enum[i % prop2.enum.length];
|
|
1257
2483
|
}
|
|
1258
|
-
switch (
|
|
2484
|
+
switch (prop2.type) {
|
|
1259
2485
|
case "string":
|
|
1260
|
-
return sampleString(
|
|
2486
|
+
return sampleString(prop2, key, i);
|
|
1261
2487
|
case "number":
|
|
1262
2488
|
return Math.round((i + 1) * 1.5 * 100) / 100;
|
|
1263
2489
|
case "integer":
|
|
@@ -1276,8 +2502,8 @@ function sampleRows(schema, count = 3) {
|
|
|
1276
2502
|
const entries = Object.entries(schema.properties);
|
|
1277
2503
|
return Array.from({ length: count }, (_, i) => {
|
|
1278
2504
|
const row = {};
|
|
1279
|
-
for (const [key,
|
|
1280
|
-
row[key] = sampleValue(
|
|
2505
|
+
for (const [key, prop2] of entries) {
|
|
2506
|
+
row[key] = sampleValue(prop2, key, i);
|
|
1281
2507
|
}
|
|
1282
2508
|
return row;
|
|
1283
2509
|
});
|
|
@@ -1289,7 +2515,7 @@ function TablePreview({
|
|
|
1289
2515
|
schema,
|
|
1290
2516
|
uiSchema
|
|
1291
2517
|
}) {
|
|
1292
|
-
const data =
|
|
2518
|
+
const data = useMemo3(() => toPageResult(sampleRows(schema)), [schema]);
|
|
1293
2519
|
return /* @__PURE__ */ jsx3(
|
|
1294
2520
|
EntityTable,
|
|
1295
2521
|
{
|
|
@@ -1304,17 +2530,282 @@ function TablePreview({
|
|
|
1304
2530
|
}
|
|
1305
2531
|
|
|
1306
2532
|
// src/designers/FormLayoutDesigner.tsx
|
|
1307
|
-
import { useMemo as
|
|
1308
|
-
import {
|
|
1309
|
-
import {
|
|
2533
|
+
import { useMemo as useMemo5, useState as useState4 } from "react";
|
|
2534
|
+
import { cn as cn2 } from "@petrarca/sonnet-core";
|
|
2535
|
+
import { Input as Input3, IconButton as IconButton2, Button } from "@petrarca/sonnet-ui";
|
|
2536
|
+
import { FormSelect as FormSelect3, JsonSchemaFormRenderer as JsonSchemaFormRenderer2 } from "@petrarca/sonnet-forms";
|
|
1310
2537
|
import {
|
|
1311
2538
|
Plus as Plus2,
|
|
1312
2539
|
Trash2 as Trash22,
|
|
1313
2540
|
ChevronUp as ChevronUp2,
|
|
1314
2541
|
ChevronDown as ChevronDown2,
|
|
2542
|
+
ChevronLeft,
|
|
2543
|
+
ChevronRight as ChevronRight2,
|
|
1315
2544
|
GripVertical
|
|
1316
2545
|
} from "lucide-react";
|
|
2546
|
+
|
|
2547
|
+
// src/inspector/RuleFields.tsx
|
|
2548
|
+
import { useEffect as useEffect2, useMemo as useMemo4, useState as useState3 } from "react";
|
|
2549
|
+
import { FormSelect as FormSelect2 } from "@petrarca/sonnet-forms";
|
|
2550
|
+
|
|
2551
|
+
// src/model/conditions.ts
|
|
2552
|
+
var CONDITION_OPERATORS = [
|
|
2553
|
+
"=",
|
|
2554
|
+
"!=",
|
|
2555
|
+
">",
|
|
2556
|
+
">=",
|
|
2557
|
+
"<",
|
|
2558
|
+
"<=",
|
|
2559
|
+
"exists"
|
|
2560
|
+
];
|
|
2561
|
+
var CONDITION_OPERATOR_LABELS = {
|
|
2562
|
+
"=": "equals",
|
|
2563
|
+
"!=": "not equals",
|
|
2564
|
+
">": "greater than",
|
|
2565
|
+
">=": "greater or equal",
|
|
2566
|
+
"<": "less than",
|
|
2567
|
+
"<=": "less or equal",
|
|
2568
|
+
exists: "exists"
|
|
2569
|
+
};
|
|
2570
|
+
var OPERATOR_OPTIONS = CONDITION_OPERATORS.map((op) => ({
|
|
2571
|
+
value: op,
|
|
2572
|
+
label: CONDITION_OPERATOR_LABELS[op]
|
|
2573
|
+
}));
|
|
2574
|
+
var NUMERIC_OPS = /* @__PURE__ */ new Set([
|
|
2575
|
+
">",
|
|
2576
|
+
">=",
|
|
2577
|
+
"<",
|
|
2578
|
+
"<="
|
|
2579
|
+
]);
|
|
2580
|
+
function coerceValue(value) {
|
|
2581
|
+
if (value.trim() === "") return value;
|
|
2582
|
+
const n = Number(value);
|
|
2583
|
+
return Number.isFinite(n) ? n : value;
|
|
2584
|
+
}
|
|
2585
|
+
function coerceForScope(value, operator, type) {
|
|
2586
|
+
if (NUMERIC_OPS.has(operator)) return coerceValue(value);
|
|
2587
|
+
if (type === "boolean") return value === "true";
|
|
2588
|
+
if (type === "number" || type === "integer") return coerceValue(value);
|
|
2589
|
+
return value;
|
|
2590
|
+
}
|
|
2591
|
+
function operatorToConstraint(operator, value, type) {
|
|
2592
|
+
const v = coerceForScope(value, operator, type);
|
|
2593
|
+
switch (operator) {
|
|
2594
|
+
case "=":
|
|
2595
|
+
return { const: v };
|
|
2596
|
+
case "!=":
|
|
2597
|
+
return { not: { const: v } };
|
|
2598
|
+
case ">":
|
|
2599
|
+
return { exclusiveMinimum: v };
|
|
2600
|
+
case ">=":
|
|
2601
|
+
return { minimum: v };
|
|
2602
|
+
case "<":
|
|
2603
|
+
return { exclusiveMaximum: v };
|
|
2604
|
+
case "<=":
|
|
2605
|
+
return { maximum: v };
|
|
2606
|
+
case "exists":
|
|
2607
|
+
return {};
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
function conditionToIf(condition2, type) {
|
|
2611
|
+
const { field, operator } = condition2;
|
|
2612
|
+
if (!field) return void 0;
|
|
2613
|
+
if (operator === "exists") {
|
|
2614
|
+
return { required: [field] };
|
|
2615
|
+
}
|
|
2616
|
+
return {
|
|
2617
|
+
properties: {
|
|
2618
|
+
[field]: operatorToConstraint(operator, condition2.value, type)
|
|
2619
|
+
},
|
|
2620
|
+
required: [field]
|
|
2621
|
+
};
|
|
2622
|
+
}
|
|
2623
|
+
function constraintToOperator(c) {
|
|
2624
|
+
if (c.not && "const" in c.not)
|
|
2625
|
+
return { operator: "!=", value: String(c.not.const) };
|
|
2626
|
+
if (c.exclusiveMinimum !== void 0)
|
|
2627
|
+
return { operator: ">", value: String(c.exclusiveMinimum) };
|
|
2628
|
+
if (c.minimum !== void 0)
|
|
2629
|
+
return { operator: ">=", value: String(c.minimum) };
|
|
2630
|
+
if (c.exclusiveMaximum !== void 0)
|
|
2631
|
+
return { operator: "<", value: String(c.exclusiveMaximum) };
|
|
2632
|
+
if (c.maximum !== void 0)
|
|
2633
|
+
return { operator: "<=", value: String(c.maximum) };
|
|
2634
|
+
if ("const" in c) return { operator: "=", value: String(c.const) };
|
|
2635
|
+
return { operator: "exists", value: "" };
|
|
2636
|
+
}
|
|
2637
|
+
function ifToCondition(ifSchema) {
|
|
2638
|
+
const empty = { field: "", operator: "=", value: "" };
|
|
2639
|
+
if (!ifSchema) return empty;
|
|
2640
|
+
const props = ifSchema.properties ?? {};
|
|
2641
|
+
const field = Object.keys(props)[0] ?? ifSchema.required?.[0] ?? "";
|
|
2642
|
+
if (!field) return empty;
|
|
2643
|
+
const constraint = props[field];
|
|
2644
|
+
if (!constraint) return { field, operator: "exists", value: "" };
|
|
2645
|
+
const { operator, value } = constraintToOperator(constraint);
|
|
2646
|
+
return { field, operator, value };
|
|
2647
|
+
}
|
|
2648
|
+
|
|
2649
|
+
// src/inspector/OperatorValueInput.tsx
|
|
2650
|
+
import { Input as Input2 } from "@petrarca/sonnet-ui";
|
|
1317
2651
|
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
2652
|
+
function OperatorValueInput({
|
|
2653
|
+
id,
|
|
2654
|
+
cacheKey,
|
|
2655
|
+
value,
|
|
2656
|
+
onCommit,
|
|
2657
|
+
disabled
|
|
2658
|
+
}) {
|
|
2659
|
+
return /* @__PURE__ */ jsxs3("div", { className: "space-y-1", children: [
|
|
2660
|
+
/* @__PURE__ */ jsx4("label", { htmlFor: id, className: "text-sm font-medium", children: "Value" }),
|
|
2661
|
+
/* @__PURE__ */ jsx4(
|
|
2662
|
+
Input2,
|
|
2663
|
+
{
|
|
2664
|
+
id,
|
|
2665
|
+
defaultValue: value,
|
|
2666
|
+
onBlur: (e) => onCommit(e.target.value),
|
|
2667
|
+
disabled,
|
|
2668
|
+
placeholder: "Value to compare"
|
|
2669
|
+
},
|
|
2670
|
+
cacheKey
|
|
2671
|
+
)
|
|
2672
|
+
] });
|
|
2673
|
+
}
|
|
2674
|
+
|
|
2675
|
+
// src/inspector/RuleFields.tsx
|
|
2676
|
+
import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
2677
|
+
function ruleToForm(rule) {
|
|
2678
|
+
if (!rule) return { effect: "", scope: "", operator: "=", value: "" };
|
|
2679
|
+
const { operator, value } = constraintToOperator(rule.condition.schema);
|
|
2680
|
+
return { effect: rule.effect, scope: rule.condition.scope, operator, value };
|
|
2681
|
+
}
|
|
2682
|
+
function formToRule(form, scopeType) {
|
|
2683
|
+
if (!form.effect || !form.scope) return void 0;
|
|
2684
|
+
return {
|
|
2685
|
+
effect: form.effect,
|
|
2686
|
+
condition: {
|
|
2687
|
+
scope: form.scope,
|
|
2688
|
+
schema: operatorToConstraint(form.operator, form.value, scopeType)
|
|
2689
|
+
}
|
|
2690
|
+
};
|
|
2691
|
+
}
|
|
2692
|
+
var BOOLEAN_VALUE_OPTIONS = [
|
|
2693
|
+
{ value: "true", label: "true" },
|
|
2694
|
+
{ value: "false", label: "false" }
|
|
2695
|
+
];
|
|
2696
|
+
function RuleFields({
|
|
2697
|
+
rule,
|
|
2698
|
+
scopes,
|
|
2699
|
+
effects,
|
|
2700
|
+
idPrefix,
|
|
2701
|
+
disabled = false,
|
|
2702
|
+
onChange
|
|
2703
|
+
}) {
|
|
2704
|
+
const [form, setForm] = useState3(() => ruleToForm(rule));
|
|
2705
|
+
const seed = JSON.stringify(rule ?? null);
|
|
2706
|
+
useEffect2(() => {
|
|
2707
|
+
setForm(ruleToForm(JSON.parse(seed) ?? void 0));
|
|
2708
|
+
}, [seed]);
|
|
2709
|
+
const scopeType = useMemo4(
|
|
2710
|
+
() => scopes.find((s) => s.pointer === form.scope)?.type,
|
|
2711
|
+
[scopes, form.scope]
|
|
2712
|
+
);
|
|
2713
|
+
const commit = (next) => {
|
|
2714
|
+
setForm(next);
|
|
2715
|
+
const nextType = scopes.find((s) => s.pointer === next.scope)?.type;
|
|
2716
|
+
const built = formToRule(next, nextType);
|
|
2717
|
+
if (built || next.effect === "") onChange(built);
|
|
2718
|
+
};
|
|
2719
|
+
const scopeOptions = useMemo4(
|
|
2720
|
+
() => scopes.map((s) => ({ value: s.pointer, label: s.pointer.slice(1) })),
|
|
2721
|
+
[scopes]
|
|
2722
|
+
);
|
|
2723
|
+
const needsValue = form.operator !== "exists";
|
|
2724
|
+
const isBoolean = scopeType === "boolean";
|
|
2725
|
+
return /* @__PURE__ */ jsxs4("div", { className: "space-y-3", children: [
|
|
2726
|
+
/* @__PURE__ */ jsx5(
|
|
2727
|
+
FormSelect2,
|
|
2728
|
+
{
|
|
2729
|
+
id: `${idPrefix}-effect`,
|
|
2730
|
+
name: "effect",
|
|
2731
|
+
label: "Effect",
|
|
2732
|
+
options: effects,
|
|
2733
|
+
value: form.effect,
|
|
2734
|
+
onChange: (v) => commit({ ...form, effect: v ?? "" }),
|
|
2735
|
+
clearable: false,
|
|
2736
|
+
disabled
|
|
2737
|
+
}
|
|
2738
|
+
),
|
|
2739
|
+
form.effect !== "" && /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
2740
|
+
/* @__PURE__ */ jsx5(
|
|
2741
|
+
FormSelect2,
|
|
2742
|
+
{
|
|
2743
|
+
id: `${idPrefix}-scope`,
|
|
2744
|
+
name: "scope",
|
|
2745
|
+
label: "When field",
|
|
2746
|
+
options: scopeOptions,
|
|
2747
|
+
value: form.scope || null,
|
|
2748
|
+
onChange: (v) => commit({ ...form, scope: v ?? "" }),
|
|
2749
|
+
placeholder: "Select a field...",
|
|
2750
|
+
clearable: false,
|
|
2751
|
+
disabled
|
|
2752
|
+
}
|
|
2753
|
+
),
|
|
2754
|
+
/* @__PURE__ */ jsx5(
|
|
2755
|
+
FormSelect2,
|
|
2756
|
+
{
|
|
2757
|
+
id: `${idPrefix}-operator`,
|
|
2758
|
+
name: "operator",
|
|
2759
|
+
label: "Operator",
|
|
2760
|
+
options: OPERATOR_OPTIONS,
|
|
2761
|
+
value: form.operator,
|
|
2762
|
+
onChange: (v) => commit({ ...form, operator: v ?? "=" }),
|
|
2763
|
+
clearable: false,
|
|
2764
|
+
disabled
|
|
2765
|
+
}
|
|
2766
|
+
),
|
|
2767
|
+
needsValue && (isBoolean ? /* @__PURE__ */ jsx5(
|
|
2768
|
+
FormSelect2,
|
|
2769
|
+
{
|
|
2770
|
+
id: `${idPrefix}-value`,
|
|
2771
|
+
name: "value",
|
|
2772
|
+
label: "Value",
|
|
2773
|
+
options: BOOLEAN_VALUE_OPTIONS,
|
|
2774
|
+
value: form.value === "true" ? "true" : "false",
|
|
2775
|
+
onChange: (v) => commit({ ...form, value: v ?? "false" }),
|
|
2776
|
+
clearable: false,
|
|
2777
|
+
disabled: disabled || !form.scope
|
|
2778
|
+
}
|
|
2779
|
+
) : /* @__PURE__ */ jsx5(
|
|
2780
|
+
OperatorValueInput,
|
|
2781
|
+
{
|
|
2782
|
+
id: `${idPrefix}-value`,
|
|
2783
|
+
cacheKey: `${form.scope}:${form.operator}`,
|
|
2784
|
+
value: form.value,
|
|
2785
|
+
onCommit: (v) => commit({ ...form, value: v }),
|
|
2786
|
+
disabled: disabled || !form.scope
|
|
2787
|
+
}
|
|
2788
|
+
))
|
|
2789
|
+
] })
|
|
2790
|
+
] });
|
|
2791
|
+
}
|
|
2792
|
+
|
|
2793
|
+
// src/inspector/ruleEffects.ts
|
|
2794
|
+
var VISIBILITY_EFFECT_OPTIONS = [
|
|
2795
|
+
{ value: "", label: "None (always shown)" },
|
|
2796
|
+
{ value: "show", label: "Show when" },
|
|
2797
|
+
{ value: "hide", label: "Hide when" }
|
|
2798
|
+
];
|
|
2799
|
+
var FIELD_EFFECT_OPTIONS = [
|
|
2800
|
+
{ value: "", label: "None (always visible)" },
|
|
2801
|
+
{ value: "show", label: "Show when" },
|
|
2802
|
+
{ value: "hide", label: "Hide when" },
|
|
2803
|
+
{ value: "enable", label: "Enable when" },
|
|
2804
|
+
{ value: "disable", label: "Disable when" }
|
|
2805
|
+
];
|
|
2806
|
+
|
|
2807
|
+
// src/designers/FormLayoutDesigner.tsx
|
|
2808
|
+
import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1318
2809
|
var DRAG_MIME = "application/x-sonnet-property";
|
|
1319
2810
|
function orderedKeysOf(properties, order) {
|
|
1320
2811
|
const keys = Object.keys(properties);
|
|
@@ -1323,21 +2814,43 @@ function orderedKeysOf(properties, order) {
|
|
|
1323
2814
|
return [...inOrder, ...keys.filter((k) => !inOrder.includes(k))];
|
|
1324
2815
|
}
|
|
1325
2816
|
function scopeTarget(schema, scopePath) {
|
|
1326
|
-
const
|
|
1327
|
-
if (!
|
|
1328
|
-
return
|
|
2817
|
+
const prop2 = getProperty(schema, scopePath);
|
|
2818
|
+
if (!prop2) return void 0;
|
|
2819
|
+
return prop2.type === "array" ? prop2.items : prop2;
|
|
2820
|
+
}
|
|
2821
|
+
function scopeContainer(schema, uiSchema, scopePath) {
|
|
2822
|
+
if (scopePath.length === 0) {
|
|
2823
|
+
return {
|
|
2824
|
+
container: schema,
|
|
2825
|
+
layout: uiSchema["x-ui-layout"]
|
|
2826
|
+
};
|
|
2827
|
+
}
|
|
2828
|
+
const container = scopeTarget(schema, scopePath);
|
|
2829
|
+
return {
|
|
2830
|
+
container,
|
|
2831
|
+
layout: container?.["x-ui-layout"]
|
|
2832
|
+
};
|
|
1329
2833
|
}
|
|
1330
2834
|
function resolveScope(schema, uiSchema, scopePath) {
|
|
1331
|
-
const container
|
|
1332
|
-
const
|
|
1333
|
-
const isArray = scopeIsArray(schema, scopePath);
|
|
2835
|
+
const { container, layout } = scopeContainer(schema, uiSchema, scopePath);
|
|
2836
|
+
const props = container?.properties ?? {};
|
|
1334
2837
|
return {
|
|
1335
|
-
keys: orderedKeysOf(
|
|
2838
|
+
keys: orderedKeysOf(props, container?.["x-ui-order"]),
|
|
2839
|
+
props,
|
|
1336
2840
|
rows: layout?.rows ?? [],
|
|
2841
|
+
groups: layout?.groups ?? [],
|
|
1337
2842
|
layout,
|
|
1338
|
-
isArray
|
|
2843
|
+
isArray: scopeIsArray(schema, scopePath)
|
|
1339
2844
|
};
|
|
1340
2845
|
}
|
|
2846
|
+
function ownsField(rows, key) {
|
|
2847
|
+
return rows.some((r) => r.fields.includes(key));
|
|
2848
|
+
}
|
|
2849
|
+
function rowTarget(grouped, groups, groupIndex, flatRows) {
|
|
2850
|
+
const rows = grouped ? groups[groupIndex]?.rows ?? [] : flatRows;
|
|
2851
|
+
const all = grouped ? groups.flatMap((t) => t.rows) : flatRows;
|
|
2852
|
+
return { rows, assigned: new Set(all.flatMap((r) => r.fields)) };
|
|
2853
|
+
}
|
|
1341
2854
|
function scopeIsArray(schema, scopePath) {
|
|
1342
2855
|
if (scopePath.length === 0) return false;
|
|
1343
2856
|
return getProperty(schema, scopePath)?.type === "array";
|
|
@@ -1377,9 +2890,9 @@ function ArrayItemLayoutBar({
|
|
|
1377
2890
|
readOnly,
|
|
1378
2891
|
onChange
|
|
1379
2892
|
}) {
|
|
1380
|
-
return /* @__PURE__ */
|
|
1381
|
-
/* @__PURE__ */
|
|
1382
|
-
/* @__PURE__ */
|
|
2893
|
+
return /* @__PURE__ */ jsxs5("div", { className: "shrink-0 rounded-lg border p-3", children: [
|
|
2894
|
+
/* @__PURE__ */ jsx6("p", { className: "heading-meta mb-2", children: "Array item layout" }),
|
|
2895
|
+
/* @__PURE__ */ jsx6(
|
|
1383
2896
|
JsonSchemaFormRenderer2,
|
|
1384
2897
|
{
|
|
1385
2898
|
schema: SCALAR_META_SCHEMA,
|
|
@@ -1396,23 +2909,37 @@ function ArrayItemLayoutBar({
|
|
|
1396
2909
|
function FormLayoutDesigner({
|
|
1397
2910
|
state
|
|
1398
2911
|
}) {
|
|
1399
|
-
const scopes =
|
|
1400
|
-
const [scopePath, setScopePath] =
|
|
1401
|
-
const scopeKey = scopePath
|
|
1402
|
-
const activeScope = scopes.find((s) => s.path
|
|
2912
|
+
const scopes = useMemo5(() => layoutScopes(state.schema), [state.schema]);
|
|
2913
|
+
const [scopePath, setScopePath] = useState4([]);
|
|
2914
|
+
const scopeKey = pathKey(scopePath);
|
|
2915
|
+
const activeScope = scopes.find((s) => pathKey(s.path) === scopeKey) ?? scopes[0];
|
|
1403
2916
|
const effectivePath = activeScope.path;
|
|
1404
2917
|
const {
|
|
1405
2918
|
keys: allKeys,
|
|
1406
|
-
|
|
2919
|
+
props: scopeProps,
|
|
2920
|
+
rows: flatRows,
|
|
2921
|
+
groups,
|
|
1407
2922
|
layout,
|
|
1408
2923
|
isArray
|
|
1409
2924
|
} = resolveScope(state.schema, state.uiSchema, effectivePath);
|
|
1410
|
-
const
|
|
2925
|
+
const grouped = groups.length > 0;
|
|
2926
|
+
const [activeGroup, setActiveGroup] = useState4(0);
|
|
2927
|
+
const groupIndex = grouped ? Math.min(activeGroup, groups.length - 1) : 0;
|
|
2928
|
+
const { rows, assigned } = rowTarget(grouped, groups, groupIndex, flatRows);
|
|
1411
2929
|
const available = allKeys.filter((k) => !assigned.has(k));
|
|
1412
|
-
const
|
|
1413
|
-
const
|
|
2930
|
+
const inOtherGroups = grouped ? allKeys.filter((k) => assigned.has(k) && !ownsField(rows, k)) : [];
|
|
2931
|
+
const [dragKey, setDragKey] = useState4(null);
|
|
2932
|
+
const commit = makeCommitRows(state, effectivePath, grouped, groupIndex);
|
|
1414
2933
|
const place = (key, rowIndex) => {
|
|
1415
2934
|
if (!key) return;
|
|
2935
|
+
if (grouped) {
|
|
2936
|
+
state.placeFieldInGroup(
|
|
2937
|
+
effectivePath,
|
|
2938
|
+
key,
|
|
2939
|
+
rowIndex === null ? null : { groupIndex, rowIndex }
|
|
2940
|
+
);
|
|
2941
|
+
return;
|
|
2942
|
+
}
|
|
1416
2943
|
const next = rows.map((r) => ({
|
|
1417
2944
|
...r,
|
|
1418
2945
|
fields: r.fields.filter((f) => f !== key)
|
|
@@ -1466,26 +2993,57 @@ function FormLayoutDesigner({
|
|
|
1466
2993
|
});
|
|
1467
2994
|
commit(next);
|
|
1468
2995
|
};
|
|
2996
|
+
const scopeOptions = allKeys.map((k) => ({
|
|
2997
|
+
pointer: `/${k}`,
|
|
2998
|
+
type: scopeProps[k]?.type
|
|
2999
|
+
}));
|
|
3000
|
+
const groupOps = makeGroupOps(
|
|
3001
|
+
state,
|
|
3002
|
+
effectivePath,
|
|
3003
|
+
groups.length,
|
|
3004
|
+
groupIndex,
|
|
3005
|
+
setActiveGroup
|
|
3006
|
+
);
|
|
1469
3007
|
const readOnly = state.readOnly;
|
|
1470
|
-
return /* @__PURE__ */
|
|
1471
|
-
/* @__PURE__ */
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
3008
|
+
return /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-3 h-full min-h-0", children: [
|
|
3009
|
+
/* @__PURE__ */ jsx6(
|
|
3010
|
+
LayoutToolbar,
|
|
3011
|
+
{
|
|
3012
|
+
scopes,
|
|
3013
|
+
scopePath: effectivePath,
|
|
3014
|
+
onScope: setScopePath,
|
|
3015
|
+
grouped,
|
|
3016
|
+
variant: layout?.variant,
|
|
3017
|
+
readOnly,
|
|
3018
|
+
onMode: groupOps.setMode,
|
|
3019
|
+
onVariant: (v) => state.setScopedLayoutVariant(effectivePath, v)
|
|
3020
|
+
}
|
|
3021
|
+
),
|
|
3022
|
+
grouped && /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
3023
|
+
/* @__PURE__ */ jsx6(
|
|
3024
|
+
GroupBar,
|
|
1475
3025
|
{
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
clearable: false
|
|
3026
|
+
groups,
|
|
3027
|
+
activeIndex: groupIndex,
|
|
3028
|
+
readOnly,
|
|
3029
|
+
onSelect: setActiveGroup,
|
|
3030
|
+
onAdd: groupOps.add,
|
|
3031
|
+
onRename: groupOps.rename,
|
|
3032
|
+
onRemove: groupOps.remove,
|
|
3033
|
+
onMove: groupOps.move
|
|
1485
3034
|
}
|
|
1486
|
-
)
|
|
3035
|
+
),
|
|
3036
|
+
/* @__PURE__ */ jsx6(
|
|
3037
|
+
GroupSettings,
|
|
3038
|
+
{
|
|
3039
|
+
group: groups[groupIndex],
|
|
3040
|
+
scopes: scopeOptions,
|
|
3041
|
+
readOnly,
|
|
3042
|
+
onRule: (rule) => state.setLayoutGroupRule(effectivePath, groupIndex, rule)
|
|
3043
|
+
}
|
|
3044
|
+
)
|
|
1487
3045
|
] }),
|
|
1488
|
-
isArray && /* @__PURE__ */
|
|
3046
|
+
isArray && /* @__PURE__ */ jsx6(
|
|
1489
3047
|
ArrayItemLayoutBar,
|
|
1490
3048
|
{
|
|
1491
3049
|
layout,
|
|
@@ -1493,38 +3051,24 @@ function FormLayoutDesigner({
|
|
|
1493
3051
|
onChange: (opts) => state.setScopedLayoutOptions(effectivePath, opts)
|
|
1494
3052
|
}
|
|
1495
3053
|
),
|
|
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(
|
|
3054
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex gap-4 flex-1 min-h-0", children: [
|
|
3055
|
+
/* @__PURE__ */ jsx6(
|
|
3056
|
+
AvailablePool,
|
|
3057
|
+
{
|
|
3058
|
+
available,
|
|
3059
|
+
inOtherGroups,
|
|
3060
|
+
grouped,
|
|
3061
|
+
readOnly,
|
|
3062
|
+
dragActive: !!dragKey,
|
|
3063
|
+
onDropToPool: (key) => place(key, null),
|
|
3064
|
+
onDragStartKey: setDragKey,
|
|
3065
|
+
onDragEndKey: () => setDragKey(null)
|
|
3066
|
+
}
|
|
3067
|
+
),
|
|
3068
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex-1 min-w-0 flex flex-col min-h-0", children: [
|
|
3069
|
+
/* @__PURE__ */ jsxs5("div", { className: "mb-2 flex items-center gap-2", children: [
|
|
3070
|
+
/* @__PURE__ */ jsx6("p", { className: "heading-meta", children: rowsHeading(grouped, groups, groupIndex) }),
|
|
3071
|
+
!readOnly && /* @__PURE__ */ jsx6("span", { className: "ml-auto", children: /* @__PURE__ */ jsxs5(
|
|
1528
3072
|
Button,
|
|
1529
3073
|
{
|
|
1530
3074
|
size: "sm",
|
|
@@ -1532,13 +3076,13 @@ function FormLayoutDesigner({
|
|
|
1532
3076
|
className: "gap-1.5",
|
|
1533
3077
|
onClick: addRow,
|
|
1534
3078
|
children: [
|
|
1535
|
-
/* @__PURE__ */
|
|
3079
|
+
/* @__PURE__ */ jsx6(Plus2, { className: "h-4 w-4" }),
|
|
1536
3080
|
"Add row"
|
|
1537
3081
|
]
|
|
1538
3082
|
}
|
|
1539
3083
|
) })
|
|
1540
3084
|
] }),
|
|
1541
|
-
/* @__PURE__ */
|
|
3085
|
+
/* @__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
3086
|
RowCard,
|
|
1543
3087
|
{
|
|
1544
3088
|
row,
|
|
@@ -1564,6 +3108,352 @@ function FormLayoutDesigner({
|
|
|
1564
3108
|
] })
|
|
1565
3109
|
] });
|
|
1566
3110
|
}
|
|
3111
|
+
function makeCommitRows(state, scopePath, grouped, groupIndex) {
|
|
3112
|
+
if (grouped) {
|
|
3113
|
+
return (rows) => state.setScopedGroupRows(scopePath, groupIndex, rows);
|
|
3114
|
+
}
|
|
3115
|
+
return (rows) => state.setScopedLayoutRows(scopePath, rows);
|
|
3116
|
+
}
|
|
3117
|
+
function rowsHeading(grouped, groups, groupIndex) {
|
|
3118
|
+
if (!grouped) return "Rows";
|
|
3119
|
+
return `Rows in ${groups[groupIndex]?.label ?? ""}`;
|
|
3120
|
+
}
|
|
3121
|
+
var VARIANT_OPTIONS = [
|
|
3122
|
+
{ value: "tabs", label: "Tabs" },
|
|
3123
|
+
{ value: "stepper", label: "Stepper" },
|
|
3124
|
+
{ value: "sections", label: "Sections" }
|
|
3125
|
+
];
|
|
3126
|
+
function makeGroupOps(state, scopePath, groupCount, activeIndex, setActiveGroup) {
|
|
3127
|
+
return {
|
|
3128
|
+
add: () => {
|
|
3129
|
+
state.addLayoutGroup(scopePath);
|
|
3130
|
+
setActiveGroup(groupCount);
|
|
3131
|
+
},
|
|
3132
|
+
rename: (index, label) => state.renameLayoutGroup(scopePath, index, label),
|
|
3133
|
+
move: (index, direction) => {
|
|
3134
|
+
state.moveLayoutGroup(scopePath, index, direction);
|
|
3135
|
+
if (index === activeIndex) setActiveGroup(activeIndex + direction);
|
|
3136
|
+
},
|
|
3137
|
+
remove: (index) => {
|
|
3138
|
+
state.removeLayoutGroup(scopePath, index);
|
|
3139
|
+
if (index <= activeIndex && activeIndex > 0)
|
|
3140
|
+
setActiveGroup(activeIndex - 1);
|
|
3141
|
+
},
|
|
3142
|
+
setMode: (mode) => {
|
|
3143
|
+
state.setScopedLayoutMode(scopePath, mode);
|
|
3144
|
+
setActiveGroup(0);
|
|
3145
|
+
}
|
|
3146
|
+
};
|
|
3147
|
+
}
|
|
3148
|
+
function AvailablePool({
|
|
3149
|
+
available,
|
|
3150
|
+
inOtherGroups,
|
|
3151
|
+
grouped,
|
|
3152
|
+
readOnly,
|
|
3153
|
+
dragActive,
|
|
3154
|
+
onDropToPool,
|
|
3155
|
+
onDragStartKey,
|
|
3156
|
+
onDragEndKey
|
|
3157
|
+
}) {
|
|
3158
|
+
const empty = available.length === 0 && inOtherGroups.length === 0;
|
|
3159
|
+
return /* @__PURE__ */ jsxs5("div", { className: "w-56 shrink-0 flex flex-col min-h-0", children: [
|
|
3160
|
+
/* @__PURE__ */ jsx6("p", { className: "heading-meta mb-2", children: "Available" }),
|
|
3161
|
+
/* @__PURE__ */ jsxs5(
|
|
3162
|
+
"div",
|
|
3163
|
+
{
|
|
3164
|
+
className: "flex-1 min-h-0 overflow-auto rounded-lg border p-2 space-y-1 bg-muted/20",
|
|
3165
|
+
onDragOver: (e) => {
|
|
3166
|
+
if (dragActive) e.preventDefault();
|
|
3167
|
+
},
|
|
3168
|
+
onDrop: (e) => {
|
|
3169
|
+
const key = e.dataTransfer.getData(DRAG_MIME);
|
|
3170
|
+
if (key) onDropToPool(key);
|
|
3171
|
+
},
|
|
3172
|
+
children: [
|
|
3173
|
+
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(
|
|
3174
|
+
Chip,
|
|
3175
|
+
{
|
|
3176
|
+
label: key,
|
|
3177
|
+
readOnly,
|
|
3178
|
+
onDragStart: () => onDragStartKey(key),
|
|
3179
|
+
onDragEnd: onDragEndKey
|
|
3180
|
+
},
|
|
3181
|
+
key
|
|
3182
|
+
)),
|
|
3183
|
+
inOtherGroups.length > 0 && /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
3184
|
+
/* @__PURE__ */ jsx6("p", { className: "pt-2 pb-1 text-[11px] uppercase tracking-wide text-muted-foreground/70", children: "In other groups" }),
|
|
3185
|
+
inOtherGroups.map((key) => /* @__PURE__ */ jsx6(
|
|
3186
|
+
Chip,
|
|
3187
|
+
{
|
|
3188
|
+
label: key,
|
|
3189
|
+
readOnly,
|
|
3190
|
+
muted: true,
|
|
3191
|
+
onDragStart: () => onDragStartKey(key),
|
|
3192
|
+
onDragEnd: onDragEndKey
|
|
3193
|
+
},
|
|
3194
|
+
key
|
|
3195
|
+
))
|
|
3196
|
+
] })
|
|
3197
|
+
]
|
|
3198
|
+
}
|
|
3199
|
+
),
|
|
3200
|
+
/* @__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." })
|
|
3201
|
+
] });
|
|
3202
|
+
}
|
|
3203
|
+
function LayoutToolbar({
|
|
3204
|
+
scopes,
|
|
3205
|
+
scopePath,
|
|
3206
|
+
onScope,
|
|
3207
|
+
grouped,
|
|
3208
|
+
variant,
|
|
3209
|
+
readOnly,
|
|
3210
|
+
onMode,
|
|
3211
|
+
onVariant
|
|
3212
|
+
}) {
|
|
3213
|
+
return /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2 shrink-0", children: [
|
|
3214
|
+
/* @__PURE__ */ jsx6("label", { className: "heading-meta", children: "Scope" }),
|
|
3215
|
+
/* @__PURE__ */ jsx6("div", { className: "w-64", children: /* @__PURE__ */ jsx6(
|
|
3216
|
+
FormSelect3,
|
|
3217
|
+
{
|
|
3218
|
+
id: "layout-scope",
|
|
3219
|
+
name: "scope",
|
|
3220
|
+
value: pathKey(scopePath),
|
|
3221
|
+
options: scopes.map((s) => ({
|
|
3222
|
+
value: pathKey(s.path),
|
|
3223
|
+
label: s.label
|
|
3224
|
+
})),
|
|
3225
|
+
onChange: (v) => onScope(v ? parsePathKey(v) : []),
|
|
3226
|
+
clearable: false
|
|
3227
|
+
}
|
|
3228
|
+
) }),
|
|
3229
|
+
/* @__PURE__ */ jsx6("label", { className: "heading-meta ml-4", children: "Group by" }),
|
|
3230
|
+
/* @__PURE__ */ jsx6(
|
|
3231
|
+
ModeToggle,
|
|
3232
|
+
{
|
|
3233
|
+
mode: grouped ? "groups" : "rows",
|
|
3234
|
+
readOnly,
|
|
3235
|
+
onChange: onMode
|
|
3236
|
+
}
|
|
3237
|
+
),
|
|
3238
|
+
grouped && /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
3239
|
+
/* @__PURE__ */ jsx6("label", { className: "heading-meta ml-4", children: "Show as" }),
|
|
3240
|
+
/* @__PURE__ */ jsx6("div", { className: "w-36", children: /* @__PURE__ */ jsx6(
|
|
3241
|
+
FormSelect3,
|
|
3242
|
+
{
|
|
3243
|
+
id: "layout-variant",
|
|
3244
|
+
name: "variant",
|
|
3245
|
+
value: variant ?? "tabs",
|
|
3246
|
+
options: VARIANT_OPTIONS,
|
|
3247
|
+
onChange: (v) => onVariant(v ?? "tabs"),
|
|
3248
|
+
clearable: false,
|
|
3249
|
+
disabled: readOnly
|
|
3250
|
+
}
|
|
3251
|
+
) })
|
|
3252
|
+
] })
|
|
3253
|
+
] });
|
|
3254
|
+
}
|
|
3255
|
+
function ModeToggle({
|
|
3256
|
+
mode,
|
|
3257
|
+
readOnly,
|
|
3258
|
+
onChange
|
|
3259
|
+
}) {
|
|
3260
|
+
return /* @__PURE__ */ jsx6("div", { className: "inline-flex rounded-md border p-0.5", "aria-label": "Group by", children: ["rows", "groups"].map((m) => /* @__PURE__ */ jsx6(
|
|
3261
|
+
"button",
|
|
3262
|
+
{
|
|
3263
|
+
type: "button",
|
|
3264
|
+
disabled: readOnly,
|
|
3265
|
+
"aria-pressed": mode === m,
|
|
3266
|
+
onClick: () => onChange(m),
|
|
3267
|
+
className: cn2(
|
|
3268
|
+
"rounded px-2 py-0.5 text-xs capitalize disabled:opacity-50",
|
|
3269
|
+
mode === m ? "bg-accent text-accent-foreground font-medium" : "text-muted-foreground hover:text-foreground"
|
|
3270
|
+
),
|
|
3271
|
+
children: m
|
|
3272
|
+
},
|
|
3273
|
+
m
|
|
3274
|
+
)) });
|
|
3275
|
+
}
|
|
3276
|
+
function GroupSettings({
|
|
3277
|
+
group,
|
|
3278
|
+
scopes,
|
|
3279
|
+
readOnly,
|
|
3280
|
+
onRule
|
|
3281
|
+
}) {
|
|
3282
|
+
const [open, setOpen] = useState4(false);
|
|
3283
|
+
if (!group) return null;
|
|
3284
|
+
const hasRule = !!group.rule;
|
|
3285
|
+
return /* @__PURE__ */ jsxs5("div", { className: "shrink-0 rounded-lg border px-3 py-2", children: [
|
|
3286
|
+
/* @__PURE__ */ jsxs5(
|
|
3287
|
+
"button",
|
|
3288
|
+
{
|
|
3289
|
+
type: "button",
|
|
3290
|
+
className: "flex w-full items-center gap-1 heading-meta",
|
|
3291
|
+
onClick: () => setOpen((v) => !v),
|
|
3292
|
+
"aria-expanded": open,
|
|
3293
|
+
children: [
|
|
3294
|
+
open ? /* @__PURE__ */ jsx6(ChevronDown2, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx6(ChevronRight2, { className: "h-3 w-3" }),
|
|
3295
|
+
"Visibility of ",
|
|
3296
|
+
group.label,
|
|
3297
|
+
/* @__PURE__ */ jsx6("span", { className: "ml-1 normal-case text-muted-foreground/70", children: hasRule ? "conditional" : "always shown" })
|
|
3298
|
+
]
|
|
3299
|
+
}
|
|
3300
|
+
),
|
|
3301
|
+
open && /* @__PURE__ */ jsx6("div", { className: "mt-3 max-w-sm", children: /* @__PURE__ */ jsx6(
|
|
3302
|
+
RuleFields,
|
|
3303
|
+
{
|
|
3304
|
+
rule: group.rule,
|
|
3305
|
+
scopes,
|
|
3306
|
+
effects: VISIBILITY_EFFECT_OPTIONS,
|
|
3307
|
+
idPrefix: "group-rule",
|
|
3308
|
+
disabled: readOnly,
|
|
3309
|
+
onChange: (rule) => onRule(rule)
|
|
3310
|
+
}
|
|
3311
|
+
) })
|
|
3312
|
+
] });
|
|
3313
|
+
}
|
|
3314
|
+
function GroupBar({
|
|
3315
|
+
groups,
|
|
3316
|
+
activeIndex,
|
|
3317
|
+
readOnly,
|
|
3318
|
+
onSelect,
|
|
3319
|
+
onAdd,
|
|
3320
|
+
onRename,
|
|
3321
|
+
onRemove,
|
|
3322
|
+
onMove
|
|
3323
|
+
}) {
|
|
3324
|
+
return /* @__PURE__ */ jsxs5(
|
|
3325
|
+
"div",
|
|
3326
|
+
{
|
|
3327
|
+
role: "tablist",
|
|
3328
|
+
"aria-label": "Layout groups",
|
|
3329
|
+
className: "shrink-0 flex items-center gap-1 rounded-lg border p-1.5 overflow-x-auto",
|
|
3330
|
+
children: [
|
|
3331
|
+
groups.map((group, i) => /* @__PURE__ */ jsx6(
|
|
3332
|
+
GroupChip,
|
|
3333
|
+
{
|
|
3334
|
+
group,
|
|
3335
|
+
active: i === activeIndex,
|
|
3336
|
+
readOnly,
|
|
3337
|
+
onSelect: () => onSelect(i),
|
|
3338
|
+
onRename: (label) => onRename(i, label),
|
|
3339
|
+
onRemove: () => onRemove(i),
|
|
3340
|
+
onMoveLeft: i > 0 ? () => onMove(i, -1) : void 0,
|
|
3341
|
+
onMoveRight: i < groups.length - 1 ? () => onMove(i, 1) : void 0
|
|
3342
|
+
},
|
|
3343
|
+
`${i}:${group.label}`
|
|
3344
|
+
)),
|
|
3345
|
+
!readOnly && /* @__PURE__ */ jsxs5(
|
|
3346
|
+
Button,
|
|
3347
|
+
{
|
|
3348
|
+
size: "sm",
|
|
3349
|
+
variant: "outline",
|
|
3350
|
+
className: "gap-1.5 shrink-0",
|
|
3351
|
+
onClick: onAdd,
|
|
3352
|
+
children: [
|
|
3353
|
+
/* @__PURE__ */ jsx6(Plus2, { className: "h-4 w-4" }),
|
|
3354
|
+
"Add group"
|
|
3355
|
+
]
|
|
3356
|
+
}
|
|
3357
|
+
)
|
|
3358
|
+
]
|
|
3359
|
+
}
|
|
3360
|
+
);
|
|
3361
|
+
}
|
|
3362
|
+
function GroupChip({
|
|
3363
|
+
group,
|
|
3364
|
+
active,
|
|
3365
|
+
readOnly,
|
|
3366
|
+
onSelect,
|
|
3367
|
+
onRename,
|
|
3368
|
+
onRemove,
|
|
3369
|
+
onMoveLeft,
|
|
3370
|
+
onMoveRight
|
|
3371
|
+
}) {
|
|
3372
|
+
const [editing, setEditing] = useState4(false);
|
|
3373
|
+
if (editing) {
|
|
3374
|
+
return /* @__PURE__ */ jsx6(
|
|
3375
|
+
Input3,
|
|
3376
|
+
{
|
|
3377
|
+
autoFocus: true,
|
|
3378
|
+
defaultValue: group.label,
|
|
3379
|
+
className: "h-7 w-32 text-sm",
|
|
3380
|
+
onBlur: (e) => {
|
|
3381
|
+
const next = e.target.value.trim();
|
|
3382
|
+
if (next && next !== group.label) onRename(next);
|
|
3383
|
+
setEditing(false);
|
|
3384
|
+
},
|
|
3385
|
+
onKeyDown: (e) => {
|
|
3386
|
+
if (e.key === "Enter") e.currentTarget.blur();
|
|
3387
|
+
if (e.key === "Escape") setEditing(false);
|
|
3388
|
+
}
|
|
3389
|
+
}
|
|
3390
|
+
);
|
|
3391
|
+
}
|
|
3392
|
+
const fieldCount = group.rows.reduce((n, r) => n + r.fields.length, 0);
|
|
3393
|
+
return /* @__PURE__ */ jsxs5(
|
|
3394
|
+
"span",
|
|
3395
|
+
{
|
|
3396
|
+
className: cn2(
|
|
3397
|
+
"group/group inline-flex shrink-0 items-center gap-1 rounded-md border pl-2 pr-1 py-0.5 text-sm",
|
|
3398
|
+
active ? "bg-accent border-accent-foreground/20" : "hover:bg-muted"
|
|
3399
|
+
),
|
|
3400
|
+
children: [
|
|
3401
|
+
/* @__PURE__ */ jsxs5(
|
|
3402
|
+
"button",
|
|
3403
|
+
{
|
|
3404
|
+
type: "button",
|
|
3405
|
+
role: "group",
|
|
3406
|
+
"aria-selected": active,
|
|
3407
|
+
className: "inline-flex items-center gap-1 py-0.5",
|
|
3408
|
+
onClick: onSelect,
|
|
3409
|
+
onDoubleClick: () => !readOnly && setEditing(true),
|
|
3410
|
+
onKeyDown: (e) => {
|
|
3411
|
+
if (e.key === "F2" && !readOnly) {
|
|
3412
|
+
e.preventDefault();
|
|
3413
|
+
setEditing(true);
|
|
3414
|
+
}
|
|
3415
|
+
},
|
|
3416
|
+
title: readOnly ? group.label : `${group.label} -- F2 to rename`,
|
|
3417
|
+
children: [
|
|
3418
|
+
group.label,
|
|
3419
|
+
/* @__PURE__ */ jsx6("span", { className: "text-xs text-muted-foreground", children: fieldCount })
|
|
3420
|
+
]
|
|
3421
|
+
}
|
|
3422
|
+
),
|
|
3423
|
+
!readOnly && /* @__PURE__ */ jsxs5("span", { className: "flex items-center opacity-0 group-hover/group:opacity-100 focus-within:opacity-100", children: [
|
|
3424
|
+
onMoveLeft && /* @__PURE__ */ jsx6(
|
|
3425
|
+
IconButton2,
|
|
3426
|
+
{
|
|
3427
|
+
label: `Move ${group.label} left`,
|
|
3428
|
+
revealOnHover: true,
|
|
3429
|
+
onClick: onMoveLeft,
|
|
3430
|
+
children: /* @__PURE__ */ jsx6(ChevronLeft, {})
|
|
3431
|
+
}
|
|
3432
|
+
),
|
|
3433
|
+
onMoveRight && /* @__PURE__ */ jsx6(
|
|
3434
|
+
IconButton2,
|
|
3435
|
+
{
|
|
3436
|
+
label: `Move ${group.label} right`,
|
|
3437
|
+
revealOnHover: true,
|
|
3438
|
+
onClick: onMoveRight,
|
|
3439
|
+
children: /* @__PURE__ */ jsx6(ChevronRight2, {})
|
|
3440
|
+
}
|
|
3441
|
+
),
|
|
3442
|
+
/* @__PURE__ */ jsx6(
|
|
3443
|
+
IconButton2,
|
|
3444
|
+
{
|
|
3445
|
+
label: `Remove ${group.label}`,
|
|
3446
|
+
tone: "destructive",
|
|
3447
|
+
revealOnHover: true,
|
|
3448
|
+
onClick: onRemove,
|
|
3449
|
+
children: /* @__PURE__ */ jsx6(Trash22, {})
|
|
3450
|
+
}
|
|
3451
|
+
)
|
|
3452
|
+
] })
|
|
3453
|
+
]
|
|
3454
|
+
}
|
|
3455
|
+
);
|
|
3456
|
+
}
|
|
1567
3457
|
var WIDTH_LABELS = {
|
|
1568
3458
|
full: "Full",
|
|
1569
3459
|
sm: "Small",
|
|
@@ -1584,9 +3474,10 @@ function Chip({
|
|
|
1584
3474
|
onDragEnd,
|
|
1585
3475
|
width,
|
|
1586
3476
|
onWidth,
|
|
1587
|
-
widthDisabled
|
|
3477
|
+
widthDisabled,
|
|
3478
|
+
muted = false
|
|
1588
3479
|
}) {
|
|
1589
|
-
return /* @__PURE__ */
|
|
3480
|
+
return /* @__PURE__ */ jsxs5(
|
|
1590
3481
|
"div",
|
|
1591
3482
|
{
|
|
1592
3483
|
draggable: !readOnly,
|
|
@@ -1596,11 +3487,14 @@ function Chip({
|
|
|
1596
3487
|
onDragStart();
|
|
1597
3488
|
},
|
|
1598
3489
|
onDragEnd,
|
|
1599
|
-
className:
|
|
3490
|
+
className: cn2(
|
|
3491
|
+
"group/chip flex items-center gap-1 rounded-md border bg-background px-2 py-1 text-sm cursor-grab active:cursor-grabbing",
|
|
3492
|
+
muted && "border-dashed text-muted-foreground"
|
|
3493
|
+
),
|
|
1600
3494
|
children: [
|
|
1601
|
-
/* @__PURE__ */
|
|
1602
|
-
/* @__PURE__ */
|
|
1603
|
-
onWidth && /* @__PURE__ */
|
|
3495
|
+
/* @__PURE__ */ jsx6(GripVertical, { className: "h-3.5 w-3.5 text-muted-foreground/60 shrink-0" }),
|
|
3496
|
+
/* @__PURE__ */ jsx6("span", { className: "truncate", children: label }),
|
|
3497
|
+
onWidth && /* @__PURE__ */ jsx6(
|
|
1604
3498
|
"select",
|
|
1605
3499
|
{
|
|
1606
3500
|
value: width ?? "full",
|
|
@@ -1608,10 +3502,10 @@ function Chip({
|
|
|
1608
3502
|
disabled: readOnly || widthDisabled,
|
|
1609
3503
|
title: widthDisabled ? "Overridden by raw CSS columns" : "Column width",
|
|
1610
3504
|
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__ */
|
|
3505
|
+
children: ["full", "sm", "md", "lg"].map((w) => /* @__PURE__ */ jsx6("option", { value: w, title: WIDTH_LABELS[w], children: WIDTH_SHORT[w] }, w))
|
|
1612
3506
|
}
|
|
1613
3507
|
),
|
|
1614
|
-
onRemove && !readOnly && /* @__PURE__ */
|
|
3508
|
+
onRemove && !readOnly && /* @__PURE__ */ jsx6(IconButton2, { label: "Remove from row", onClick: onRemove, children: /* @__PURE__ */ jsx6(Trash22, {}) })
|
|
1615
3509
|
]
|
|
1616
3510
|
}
|
|
1617
3511
|
);
|
|
@@ -1634,48 +3528,48 @@ function RowCard({
|
|
|
1634
3528
|
onWidth,
|
|
1635
3529
|
onArrange
|
|
1636
3530
|
}) {
|
|
1637
|
-
const [over, setOver] =
|
|
3531
|
+
const [over, setOver] = useState4(false);
|
|
1638
3532
|
const isFlow = row.arrange === "flow";
|
|
1639
|
-
return /* @__PURE__ */
|
|
1640
|
-
/* @__PURE__ */
|
|
1641
|
-
/* @__PURE__ */
|
|
3533
|
+
return /* @__PURE__ */ jsxs5("div", { className: "rounded-lg border p-3", children: [
|
|
3534
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2 mb-2", children: [
|
|
3535
|
+
/* @__PURE__ */ jsxs5("span", { className: "heading-meta", children: [
|
|
1642
3536
|
"Row ",
|
|
1643
3537
|
index + 1
|
|
1644
3538
|
] }),
|
|
1645
|
-
!readOnly && /* @__PURE__ */
|
|
1646
|
-
/* @__PURE__ */
|
|
3539
|
+
!readOnly && /* @__PURE__ */ jsxs5("span", { className: "ml-auto flex gap-0.5", children: [
|
|
3540
|
+
/* @__PURE__ */ jsx6(
|
|
1647
3541
|
IconButton2,
|
|
1648
3542
|
{
|
|
1649
3543
|
label: "Move row up",
|
|
1650
3544
|
disabled: index === 0,
|
|
1651
3545
|
onClick: onMoveUp,
|
|
1652
|
-
children: /* @__PURE__ */
|
|
3546
|
+
children: /* @__PURE__ */ jsx6(ChevronUp2, {})
|
|
1653
3547
|
}
|
|
1654
3548
|
),
|
|
1655
|
-
/* @__PURE__ */
|
|
3549
|
+
/* @__PURE__ */ jsx6(
|
|
1656
3550
|
IconButton2,
|
|
1657
3551
|
{
|
|
1658
3552
|
label: "Move row down",
|
|
1659
3553
|
disabled: index === total - 1,
|
|
1660
3554
|
onClick: onMoveDown,
|
|
1661
|
-
children: /* @__PURE__ */
|
|
3555
|
+
children: /* @__PURE__ */ jsx6(ChevronDown2, {})
|
|
1662
3556
|
}
|
|
1663
3557
|
),
|
|
1664
|
-
/* @__PURE__ */
|
|
3558
|
+
/* @__PURE__ */ jsx6(
|
|
1665
3559
|
IconButton2,
|
|
1666
3560
|
{
|
|
1667
3561
|
label: "Delete row",
|
|
1668
3562
|
tone: "destructive",
|
|
1669
3563
|
onClick: onDelete,
|
|
1670
|
-
children: /* @__PURE__ */
|
|
3564
|
+
children: /* @__PURE__ */ jsx6(Trash22, {})
|
|
1671
3565
|
}
|
|
1672
3566
|
)
|
|
1673
3567
|
] })
|
|
1674
3568
|
] }),
|
|
1675
|
-
/* @__PURE__ */
|
|
1676
|
-
/* @__PURE__ */
|
|
1677
|
-
/* @__PURE__ */
|
|
1678
|
-
|
|
3569
|
+
/* @__PURE__ */ jsxs5("div", { className: "mb-2 flex items-center gap-2", children: [
|
|
3570
|
+
/* @__PURE__ */ jsx6("label", { className: "text-xs text-muted-foreground w-16", children: "Group" }),
|
|
3571
|
+
/* @__PURE__ */ jsx6(
|
|
3572
|
+
Input3,
|
|
1679
3573
|
{
|
|
1680
3574
|
value: row.group ?? "",
|
|
1681
3575
|
onChange: (e) => onGroup(e.target.value),
|
|
@@ -1685,9 +3579,9 @@ function RowCard({
|
|
|
1685
3579
|
}
|
|
1686
3580
|
)
|
|
1687
3581
|
] }),
|
|
1688
|
-
/* @__PURE__ */
|
|
1689
|
-
/* @__PURE__ */
|
|
1690
|
-
/* @__PURE__ */
|
|
3582
|
+
/* @__PURE__ */ jsxs5("div", { className: "mb-2 flex items-center gap-2", children: [
|
|
3583
|
+
/* @__PURE__ */ jsx6("label", { className: "text-xs text-muted-foreground w-16", children: "Arrange" }),
|
|
3584
|
+
/* @__PURE__ */ jsxs5(
|
|
1691
3585
|
"select",
|
|
1692
3586
|
{
|
|
1693
3587
|
value: isFlow ? "flow" : "grid",
|
|
@@ -1696,13 +3590,13 @@ function RowCard({
|
|
|
1696
3590
|
title: "Grid: sized columns. Inline: fields at natural width, wrapping.",
|
|
1697
3591
|
className: "h-7 rounded-md border bg-background px-2 text-sm text-foreground disabled:opacity-50",
|
|
1698
3592
|
children: [
|
|
1699
|
-
/* @__PURE__ */
|
|
1700
|
-
/* @__PURE__ */
|
|
3593
|
+
/* @__PURE__ */ jsx6("option", { value: "grid", children: "Grid (columns)" }),
|
|
3594
|
+
/* @__PURE__ */ jsx6("option", { value: "flow", children: "Inline (natural width)" })
|
|
1701
3595
|
]
|
|
1702
3596
|
}
|
|
1703
3597
|
)
|
|
1704
3598
|
] }),
|
|
1705
|
-
/* @__PURE__ */
|
|
3599
|
+
/* @__PURE__ */ jsx6(
|
|
1706
3600
|
"div",
|
|
1707
3601
|
{
|
|
1708
3602
|
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 +3611,7 @@ function RowCard({
|
|
|
1717
3611
|
setOver(false);
|
|
1718
3612
|
onDropKey(e.dataTransfer.getData(DRAG_MIME) || null);
|
|
1719
3613
|
},
|
|
1720
|
-
children: row.fields.length === 0 ? /* @__PURE__ */
|
|
3614
|
+
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
3615
|
Chip,
|
|
1722
3616
|
{
|
|
1723
3617
|
label: key,
|
|
@@ -1733,12 +3627,12 @@ function RowCard({
|
|
|
1733
3627
|
))
|
|
1734
3628
|
}
|
|
1735
3629
|
),
|
|
1736
|
-
!isFlow && /* @__PURE__ */
|
|
1737
|
-
/* @__PURE__ */
|
|
1738
|
-
/* @__PURE__ */
|
|
1739
|
-
/* @__PURE__ */
|
|
1740
|
-
/* @__PURE__ */
|
|
1741
|
-
|
|
3630
|
+
!isFlow && /* @__PURE__ */ jsxs5("details", { className: "mt-2", children: [
|
|
3631
|
+
/* @__PURE__ */ jsx6("summary", { className: "cursor-pointer text-xs text-muted-foreground", children: "Advanced: raw CSS columns" }),
|
|
3632
|
+
/* @__PURE__ */ jsxs5("div", { className: "mt-2 flex items-center gap-2", children: [
|
|
3633
|
+
/* @__PURE__ */ jsx6("label", { className: "text-xs text-muted-foreground w-16", children: "Columns" }),
|
|
3634
|
+
/* @__PURE__ */ jsx6(
|
|
3635
|
+
Input3,
|
|
1742
3636
|
{
|
|
1743
3637
|
value: row.columns ?? "",
|
|
1744
3638
|
onChange: (e) => onColumns(e.target.value),
|
|
@@ -1748,9 +3642,9 @@ function RowCard({
|
|
|
1748
3642
|
}
|
|
1749
3643
|
)
|
|
1750
3644
|
] }),
|
|
1751
|
-
/* @__PURE__ */
|
|
3645
|
+
/* @__PURE__ */ jsxs5("p", { className: "mt-1 pl-[4.5rem] text-xs text-muted-foreground", children: [
|
|
1752
3646
|
"Raw ",
|
|
1753
|
-
/* @__PURE__ */
|
|
3647
|
+
/* @__PURE__ */ jsx6("span", { className: "font-mono", children: "grid-template-columns" }),
|
|
1754
3648
|
". When set, it overrides the per-property widths. Blank = use widths."
|
|
1755
3649
|
] })
|
|
1756
3650
|
] })
|
|
@@ -1758,21 +3652,44 @@ function RowCard({
|
|
|
1758
3652
|
}
|
|
1759
3653
|
|
|
1760
3654
|
// src/preview/CapabilitiesPanel.tsx
|
|
1761
|
-
import {
|
|
3655
|
+
import { useMemo as useMemo6 } from "react";
|
|
3656
|
+
|
|
3657
|
+
// src/preview/SchemaSourcePanel.tsx
|
|
3658
|
+
import { useEffect as useEffect3, useRef as useRef3, useState as useState5 } from "react";
|
|
3659
|
+
import { Check, Copy } from "lucide-react";
|
|
1762
3660
|
import { Button as Button2 } from "@petrarca/sonnet-ui";
|
|
1763
3661
|
import { JsonEditor } from "@petrarca/sonnet-ui/json-editor";
|
|
1764
|
-
import {
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
3662
|
+
import { copyTextToClipboard } from "@petrarca/sonnet-core";
|
|
3663
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
3664
|
+
function SchemaSourcePanel({
|
|
3665
|
+
value,
|
|
3666
|
+
editable,
|
|
3667
|
+
onApply,
|
|
3668
|
+
validate
|
|
1768
3669
|
}) {
|
|
1769
|
-
const [text, setText] =
|
|
1770
|
-
|
|
1771
|
-
);
|
|
1772
|
-
const [
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
3670
|
+
const [text, setText] = useState5(value);
|
|
3671
|
+
const [error, setError] = useState5(null);
|
|
3672
|
+
const [copied, setCopied] = useState5(false);
|
|
3673
|
+
const [dirty, setDirty] = useState5(false);
|
|
3674
|
+
const lastValueRef = useRef3(value);
|
|
3675
|
+
useEffect3(() => {
|
|
3676
|
+
if (value === lastValueRef.current) return;
|
|
3677
|
+
if (!dirty) {
|
|
3678
|
+
lastValueRef.current = value;
|
|
3679
|
+
setText(value);
|
|
3680
|
+
setError(null);
|
|
3681
|
+
}
|
|
3682
|
+
}, [value, dirty]);
|
|
3683
|
+
const handleEditorChange = (next) => {
|
|
3684
|
+
setText(next);
|
|
3685
|
+
setDirty(next !== value);
|
|
3686
|
+
};
|
|
3687
|
+
const handleCopy = () => {
|
|
3688
|
+
void copyTextToClipboard(editable ? text : value).then(() => {
|
|
3689
|
+
setCopied(true);
|
|
3690
|
+
setTimeout(() => setCopied(false), 1500);
|
|
3691
|
+
});
|
|
3692
|
+
};
|
|
1776
3693
|
const handleApply = () => {
|
|
1777
3694
|
let parsed;
|
|
1778
3695
|
try {
|
|
@@ -1781,81 +3698,121 @@ function CapabilitiesPanel({
|
|
|
1781
3698
|
setError("Invalid JSON.");
|
|
1782
3699
|
return;
|
|
1783
3700
|
}
|
|
1784
|
-
const
|
|
1785
|
-
if (
|
|
1786
|
-
setError(
|
|
3701
|
+
const validationError = validate?.(parsed);
|
|
3702
|
+
if (validationError) {
|
|
3703
|
+
setError(validationError);
|
|
1787
3704
|
return;
|
|
1788
3705
|
}
|
|
1789
3706
|
setError(null);
|
|
1790
|
-
|
|
3707
|
+
setDirty(false);
|
|
3708
|
+
onApply(parsed);
|
|
1791
3709
|
};
|
|
1792
|
-
return /* @__PURE__ */
|
|
1793
|
-
/* @__PURE__ */
|
|
1794
|
-
/* @__PURE__ */
|
|
1795
|
-
/* @__PURE__ */
|
|
3710
|
+
return /* @__PURE__ */ jsxs6("div", { className: "absolute inset-0 rounded-md border overflow-hidden", children: [
|
|
3711
|
+
/* @__PURE__ */ jsxs6("div", { className: "absolute right-2 top-2 z-10 flex items-center gap-2", children: [
|
|
3712
|
+
error && /* @__PURE__ */ jsx7("span", { className: "rounded bg-destructive px-2 py-1 text-xs font-medium text-destructive-foreground", children: error }),
|
|
3713
|
+
editable && /* @__PURE__ */ jsx7(
|
|
1796
3714
|
Button2,
|
|
1797
3715
|
{
|
|
1798
3716
|
type: "button",
|
|
1799
3717
|
size: "sm",
|
|
1800
3718
|
onClick: handleApply,
|
|
1801
|
-
|
|
3719
|
+
disabled: !dirty,
|
|
1802
3720
|
children: "Apply"
|
|
1803
3721
|
}
|
|
3722
|
+
),
|
|
3723
|
+
/* @__PURE__ */ jsxs6(
|
|
3724
|
+
Button2,
|
|
3725
|
+
{
|
|
3726
|
+
type: "button",
|
|
3727
|
+
variant: "outline",
|
|
3728
|
+
size: "sm",
|
|
3729
|
+
onClick: handleCopy,
|
|
3730
|
+
className: "gap-1.5",
|
|
3731
|
+
children: [
|
|
3732
|
+
copied ? /* @__PURE__ */ jsx7(Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx7(Copy, { className: "h-3.5 w-3.5" }),
|
|
3733
|
+
copied ? "Copied" : "Copy"
|
|
3734
|
+
]
|
|
3735
|
+
}
|
|
1804
3736
|
)
|
|
1805
3737
|
] }),
|
|
1806
|
-
|
|
1807
|
-
/* @__PURE__ */ jsx5("div", { className: "relative flex-1 min-h-0 rounded-md border overflow-hidden", children: /* @__PURE__ */ jsx5(
|
|
3738
|
+
editable ? /* @__PURE__ */ jsx7(
|
|
1808
3739
|
JsonEditor,
|
|
1809
3740
|
{
|
|
1810
3741
|
value: text,
|
|
1811
3742
|
editablePaths: ["/"],
|
|
1812
3743
|
fillContainer: true,
|
|
1813
|
-
onChange:
|
|
3744
|
+
onChange: handleEditorChange
|
|
1814
3745
|
}
|
|
1815
|
-
)
|
|
3746
|
+
) : /* @__PURE__ */ jsx7(
|
|
3747
|
+
JsonEditor,
|
|
3748
|
+
{
|
|
3749
|
+
value,
|
|
3750
|
+
editablePaths: [],
|
|
3751
|
+
fillContainer: true
|
|
3752
|
+
},
|
|
3753
|
+
value
|
|
3754
|
+
)
|
|
1816
3755
|
] });
|
|
1817
3756
|
}
|
|
1818
3757
|
|
|
1819
|
-
// src/preview/
|
|
1820
|
-
import {
|
|
1821
|
-
function
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
return /* @__PURE__ */
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
]
|
|
3758
|
+
// src/preview/CapabilitiesPanel.tsx
|
|
3759
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
3760
|
+
function CapabilitiesPanel({
|
|
3761
|
+
capabilities,
|
|
3762
|
+
onApply
|
|
3763
|
+
}) {
|
|
3764
|
+
const value = useMemo6(
|
|
3765
|
+
() => JSON.stringify(capabilities ?? {}, null, 2),
|
|
3766
|
+
[capabilities]
|
|
3767
|
+
);
|
|
3768
|
+
return /* @__PURE__ */ jsx8(
|
|
3769
|
+
SchemaSourcePanel,
|
|
3770
|
+
{
|
|
3771
|
+
value,
|
|
3772
|
+
editable: true,
|
|
3773
|
+
validate: (parsed) => {
|
|
3774
|
+
const result = validateCapabilities(parsed);
|
|
3775
|
+
return result.ok ? null : result.errors.join("; ");
|
|
3776
|
+
},
|
|
3777
|
+
onApply: (parsed) => {
|
|
3778
|
+
const result = validateCapabilities(parsed);
|
|
3779
|
+
if (result.ok) onApply?.(result.value);
|
|
1842
3780
|
}
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
3781
|
+
}
|
|
3782
|
+
);
|
|
3783
|
+
}
|
|
3784
|
+
|
|
3785
|
+
// src/preview/PreviewPane.tsx
|
|
3786
|
+
import { Fragment as Fragment4, jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
3787
|
+
function validateRootSchema(parsed) {
|
|
3788
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
3789
|
+
return "Schema must be a JSON object.";
|
|
3790
|
+
}
|
|
3791
|
+
const s = parsed;
|
|
3792
|
+
if (s.type !== "object") return 'Root schema must have type: "object".';
|
|
3793
|
+
if (typeof s.properties !== "object" || s.properties === null) {
|
|
3794
|
+
return "Root schema must have a properties object.";
|
|
3795
|
+
}
|
|
3796
|
+
return null;
|
|
3797
|
+
}
|
|
3798
|
+
function validateUiSchema(parsed) {
|
|
3799
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
3800
|
+
return "UI Schema must be a JSON object.";
|
|
3801
|
+
}
|
|
3802
|
+
return null;
|
|
1846
3803
|
}
|
|
1847
3804
|
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__ */
|
|
3805
|
+
return /* @__PURE__ */ jsxs7(TabsList, { className: "mb-3", children: [
|
|
3806
|
+
flags.showForm && /* @__PURE__ */ jsx9(TabsTrigger, { value: "form", children: "Form" }),
|
|
3807
|
+
flags.isFull && flags.showForm && /* @__PURE__ */ jsx9(TabsTrigger, { value: "layout", children: "Form Layout" }),
|
|
3808
|
+
flags.showTable && /* @__PURE__ */ jsx9(TabsTrigger, { value: "table", children: "Table" }),
|
|
3809
|
+
/* @__PURE__ */ jsx9(TabsSeparator, {}),
|
|
3810
|
+
flags.showJson && /* @__PURE__ */ jsx9(TabsTrigger, { value: "schema", children: "JSON Schema" }),
|
|
3811
|
+
flags.isFull && /* @__PURE__ */ jsx9(TabsTrigger, { value: "ui", children: "UI Schema" }),
|
|
3812
|
+
flags.isFull && /* @__PURE__ */ jsx9(TabsTrigger, { value: "combined", children: "Combined" }),
|
|
3813
|
+
flags.showCapabilities && /* @__PURE__ */ jsxs7(Fragment4, { children: [
|
|
3814
|
+
/* @__PURE__ */ jsx9(TabsSeparator, {}),
|
|
3815
|
+
/* @__PURE__ */ jsx9(TabsTrigger, { value: "capabilities", children: "Capabilities" })
|
|
1859
3816
|
] })
|
|
1860
3817
|
] });
|
|
1861
3818
|
}
|
|
@@ -1873,22 +3830,22 @@ function PreviewPane({
|
|
|
1873
3830
|
showJson: caps.previews.includes("json"),
|
|
1874
3831
|
showCapabilities: caps.showCapabilities
|
|
1875
3832
|
};
|
|
1876
|
-
const [data, setData] =
|
|
3833
|
+
const [data, setData] = useState6(
|
|
1877
3834
|
() => extractSchemaDefaults(schema)
|
|
1878
3835
|
);
|
|
1879
|
-
const schemaText =
|
|
1880
|
-
const uiSchemaText =
|
|
3836
|
+
const schemaText = useMemo7(() => JSON.stringify(schema, null, 2), [schema]);
|
|
3837
|
+
const uiSchemaText = useMemo7(
|
|
1881
3838
|
() => JSON.stringify(uiSchema ?? {}, null, 2),
|
|
1882
3839
|
[uiSchema]
|
|
1883
3840
|
);
|
|
1884
|
-
const embeddedText =
|
|
3841
|
+
const embeddedText = useMemo7(
|
|
1885
3842
|
() => JSON.stringify(embedUiSchema(schema, uiSchema ?? {}), null, 2),
|
|
1886
3843
|
[schema, uiSchema]
|
|
1887
3844
|
);
|
|
1888
3845
|
const defaultTab = flags.showForm ? "form" : flags.showTable ? "table" : "schema";
|
|
1889
|
-
return /* @__PURE__ */
|
|
1890
|
-
/* @__PURE__ */
|
|
1891
|
-
flags.showForm && /* @__PURE__ */
|
|
3846
|
+
return /* @__PURE__ */ jsxs7(Tabs, { defaultValue: defaultTab, className: "flex flex-col h-full min-h-0", children: [
|
|
3847
|
+
/* @__PURE__ */ jsx9(PreviewTabList, { flags }),
|
|
3848
|
+
flags.showForm && /* @__PURE__ */ jsx9(TabsContent, { value: "form", className: "flex-1 min-h-0 overflow-auto", children: /* @__PURE__ */ jsx9(
|
|
1892
3849
|
JsonSchemaFormRenderer3,
|
|
1893
3850
|
{
|
|
1894
3851
|
schema,
|
|
@@ -1899,11 +3856,12 @@ function PreviewPane({
|
|
|
1899
3856
|
showExtraProperties: false
|
|
1900
3857
|
}
|
|
1901
3858
|
) }),
|
|
1902
|
-
flags.isFull && flags.showForm && /* @__PURE__ */
|
|
1903
|
-
flags.showTable && /* @__PURE__ */
|
|
1904
|
-
/* @__PURE__ */
|
|
3859
|
+
flags.isFull && flags.showForm && /* @__PURE__ */ jsx9(TabsContent, { value: "layout", className: "flex-1 min-h-0 overflow-hidden", children: /* @__PURE__ */ jsx9(FormLayoutDesigner, { state }) }),
|
|
3860
|
+
flags.showTable && /* @__PURE__ */ jsx9(TabsContent, { value: "table", className: "flex-1 min-h-0 overflow-auto", children: /* @__PURE__ */ jsx9(TablePreview, { schema }) }),
|
|
3861
|
+
/* @__PURE__ */ jsx9(
|
|
1905
3862
|
SourceTabPanels,
|
|
1906
3863
|
{
|
|
3864
|
+
state,
|
|
1907
3865
|
flags,
|
|
1908
3866
|
schemaText,
|
|
1909
3867
|
uiSchemaText,
|
|
@@ -1915,6 +3873,7 @@ function PreviewPane({
|
|
|
1915
3873
|
] });
|
|
1916
3874
|
}
|
|
1917
3875
|
function SourceTabPanels({
|
|
3876
|
+
state,
|
|
1918
3877
|
flags,
|
|
1919
3878
|
schemaText,
|
|
1920
3879
|
uiSchemaText,
|
|
@@ -1922,11 +3881,36 @@ function SourceTabPanels({
|
|
|
1922
3881
|
capabilities,
|
|
1923
3882
|
onCapabilitiesChange
|
|
1924
3883
|
}) {
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
flags.
|
|
1928
|
-
|
|
1929
|
-
|
|
3884
|
+
const canEditSource = !state.capabilities.locked && !state.capabilities.scoped;
|
|
3885
|
+
return /* @__PURE__ */ jsxs7(Fragment4, { children: [
|
|
3886
|
+
flags.showJson && /* @__PURE__ */ jsx9(TabsContent, { value: "schema", className: "flex-1 min-h-0 relative", children: /* @__PURE__ */ jsx9(
|
|
3887
|
+
SchemaSourcePanel,
|
|
3888
|
+
{
|
|
3889
|
+
value: schemaText,
|
|
3890
|
+
editable: canEditSource,
|
|
3891
|
+
validate: validateRootSchema,
|
|
3892
|
+
onApply: (parsed) => state.replaceSchema(parsed)
|
|
3893
|
+
}
|
|
3894
|
+
) }),
|
|
3895
|
+
flags.isFull && /* @__PURE__ */ jsx9(TabsContent, { value: "ui", className: "flex-1 min-h-0 relative", children: /* @__PURE__ */ jsx9(
|
|
3896
|
+
SchemaSourcePanel,
|
|
3897
|
+
{
|
|
3898
|
+
value: uiSchemaText,
|
|
3899
|
+
editable: canEditSource,
|
|
3900
|
+
validate: validateUiSchema,
|
|
3901
|
+
onApply: (parsed) => state.replaceUiSchema(parsed)
|
|
3902
|
+
}
|
|
3903
|
+
) }),
|
|
3904
|
+
flags.isFull && /* @__PURE__ */ jsx9(TabsContent, { value: "combined", className: "flex-1 min-h-0 relative", children: /* @__PURE__ */ jsx9(
|
|
3905
|
+
SchemaSourcePanel,
|
|
3906
|
+
{
|
|
3907
|
+
value: embeddedText,
|
|
3908
|
+
editable: false,
|
|
3909
|
+
onApply: () => {
|
|
3910
|
+
}
|
|
3911
|
+
}
|
|
3912
|
+
) }),
|
|
3913
|
+
flags.showCapabilities && /* @__PURE__ */ jsx9(TabsContent, { value: "capabilities", className: "flex-1 min-h-0 relative", children: /* @__PURE__ */ jsx9(
|
|
1930
3914
|
CapabilitiesPanel,
|
|
1931
3915
|
{
|
|
1932
3916
|
capabilities,
|
|
@@ -1937,20 +3921,27 @@ function SourceTabPanels({
|
|
|
1937
3921
|
}
|
|
1938
3922
|
|
|
1939
3923
|
// src/inspector/Inspector.tsx
|
|
1940
|
-
import { useEffect as
|
|
3924
|
+
import { useEffect as useEffect5, useMemo as useMemo14, useRef as useRef6, useState as useState8 } from "react";
|
|
1941
3925
|
import {
|
|
1942
3926
|
Tabs as Tabs2,
|
|
1943
3927
|
TabsList as TabsList2,
|
|
1944
3928
|
TabsTrigger as TabsTrigger2,
|
|
1945
3929
|
TabsContent as TabsContent2,
|
|
1946
|
-
Input as
|
|
3930
|
+
Input as Input5,
|
|
1947
3931
|
IconButton as IconButton4
|
|
1948
3932
|
} from "@petrarca/sonnet-ui";
|
|
1949
3933
|
import { Pencil, Check as Check2, X } from "lucide-react";
|
|
1950
3934
|
|
|
1951
3935
|
// src/inspector/DataTab.tsx
|
|
1952
|
-
import { useMemo as
|
|
3936
|
+
import { useMemo as useMemo8, useRef as useRef4 } from "react";
|
|
1953
3937
|
import { JsonSchemaFormRenderer as JsonSchemaFormRenderer4 } from "@petrarca/sonnet-forms";
|
|
3938
|
+
import {
|
|
3939
|
+
Select,
|
|
3940
|
+
SelectTrigger,
|
|
3941
|
+
SelectValue,
|
|
3942
|
+
SelectContent,
|
|
3943
|
+
SelectItem
|
|
3944
|
+
} from "@petrarca/sonnet-ui";
|
|
1954
3945
|
|
|
1955
3946
|
// src/inspector/metaSchemas.ts
|
|
1956
3947
|
var PROPERTY_TYPES = [
|
|
@@ -1959,8 +3950,20 @@ var PROPERTY_TYPES = [
|
|
|
1959
3950
|
"integer",
|
|
1960
3951
|
"boolean",
|
|
1961
3952
|
"object",
|
|
1962
|
-
"array"
|
|
3953
|
+
"array",
|
|
3954
|
+
"null"
|
|
1963
3955
|
];
|
|
3956
|
+
var PROPERTY_TYPE_CHOICES = [...PROPERTY_TYPES, "oneOf"];
|
|
3957
|
+
var PROPERTY_TYPE_NAMES = {
|
|
3958
|
+
string: "String",
|
|
3959
|
+
number: "Number",
|
|
3960
|
+
integer: "Integer",
|
|
3961
|
+
boolean: "Boolean",
|
|
3962
|
+
object: "Object",
|
|
3963
|
+
array: "Array",
|
|
3964
|
+
null: "Display (text only)",
|
|
3965
|
+
oneOf: "One of (variant)"
|
|
3966
|
+
};
|
|
1964
3967
|
var STRING_FORMATS = [
|
|
1965
3968
|
"",
|
|
1966
3969
|
"email",
|
|
@@ -1995,37 +3998,100 @@ function validationProps(type) {
|
|
|
1995
3998
|
return {};
|
|
1996
3999
|
}
|
|
1997
4000
|
}
|
|
1998
|
-
|
|
4001
|
+
var BRANCH_META_TYPE = "branch";
|
|
4002
|
+
function typeTraits(type) {
|
|
4003
|
+
const isDisplay = type === "null";
|
|
4004
|
+
const isOneOf3 = type === "oneOf";
|
|
1999
4005
|
const isContainer2 = type === "object" || type === "array";
|
|
4006
|
+
return {
|
|
4007
|
+
isDisplay,
|
|
4008
|
+
isOneOf: isOneOf3,
|
|
4009
|
+
// Containers, display (null), and oneOf have no meaningful default value.
|
|
4010
|
+
noDefault: isContainer2 || isDisplay || isOneOf3,
|
|
4011
|
+
// Display notes and oneOf unions are never "required" on their own.
|
|
4012
|
+
noRequired: isDisplay || isOneOf3,
|
|
4013
|
+
// A fixed value (const) is only meaningful on scalar properties.
|
|
4014
|
+
isScalar: type === "string" || type === "number" || type === "integer" || type === "boolean"
|
|
4015
|
+
};
|
|
4016
|
+
}
|
|
4017
|
+
function buildReferenceMetaSchema() {
|
|
4018
|
+
return {
|
|
4019
|
+
title: "",
|
|
4020
|
+
type: "object",
|
|
4021
|
+
properties: {
|
|
4022
|
+
title: { type: "string", title: "Label (title)" },
|
|
4023
|
+
description: {
|
|
4024
|
+
type: "string",
|
|
4025
|
+
title: "Description",
|
|
4026
|
+
"x-ui-widget": "textarea"
|
|
4027
|
+
},
|
|
4028
|
+
required: { type: "boolean", title: "Required" },
|
|
4029
|
+
default: { type: "string", title: "Default" }
|
|
4030
|
+
}
|
|
4031
|
+
};
|
|
4032
|
+
}
|
|
4033
|
+
function buildDataMetaSchema(type) {
|
|
4034
|
+
if (type === BRANCH_META_TYPE) {
|
|
4035
|
+
return {
|
|
4036
|
+
title: "",
|
|
4037
|
+
type: "object",
|
|
4038
|
+
properties: {
|
|
4039
|
+
title: { type: "string", title: "Branch label (title)" }
|
|
4040
|
+
}
|
|
4041
|
+
};
|
|
4042
|
+
}
|
|
4043
|
+
const { isDisplay, noDefault, noRequired, isScalar, isOneOf: isOneOf3 } = typeTraits(type);
|
|
2000
4044
|
const properties = {
|
|
2001
|
-
type: {
|
|
2002
|
-
|
|
4045
|
+
type: {
|
|
4046
|
+
type: "string",
|
|
4047
|
+
title: "Type",
|
|
4048
|
+
enum: [...PROPERTY_TYPE_CHOICES],
|
|
4049
|
+
"x-ui-options": { enumNames: PROPERTY_TYPE_NAMES }
|
|
4050
|
+
},
|
|
4051
|
+
title: { type: "string", title: isDisplay ? "Text" : "Label (title)" },
|
|
2003
4052
|
description: {
|
|
2004
4053
|
type: "string",
|
|
2005
4054
|
title: "Description",
|
|
2006
4055
|
"x-ui-widget": "textarea"
|
|
2007
4056
|
},
|
|
2008
|
-
|
|
2009
|
-
|
|
4057
|
+
...isOneOf3 ? {
|
|
4058
|
+
discriminator: {
|
|
4059
|
+
type: "string",
|
|
4060
|
+
title: "Discriminator property",
|
|
4061
|
+
"x-ui-description": "Shared property whose const value tags each branch. Auto-detected when blank."
|
|
4062
|
+
}
|
|
4063
|
+
} : {},
|
|
4064
|
+
...noRequired ? {} : { required: { type: "boolean", title: "Required" } },
|
|
4065
|
+
...noDefault ? {} : { default: { type: "string", title: "Default" } },
|
|
4066
|
+
...isScalar ? {
|
|
4067
|
+
const: {
|
|
4068
|
+
type: "string",
|
|
4069
|
+
title: "Const (fixed value)",
|
|
4070
|
+
"x-ui-description": "Fixed value this property must equal. Leave blank for none."
|
|
4071
|
+
}
|
|
4072
|
+
} : {},
|
|
2010
4073
|
...validationProps(type)
|
|
2011
4074
|
};
|
|
2012
4075
|
return { title: "", type: "object", properties };
|
|
2013
4076
|
}
|
|
2014
|
-
function propertyToDataValues(
|
|
4077
|
+
function propertyToDataValues(prop2, required) {
|
|
2015
4078
|
return {
|
|
2016
|
-
type:
|
|
2017
|
-
title:
|
|
2018
|
-
description:
|
|
2019
|
-
default:
|
|
4079
|
+
type: prop2.oneOf ? "oneOf" : prop2.type ?? "string",
|
|
4080
|
+
title: prop2.title,
|
|
4081
|
+
description: prop2.description,
|
|
4082
|
+
default: prop2.default,
|
|
4083
|
+
// const may be any JSON type; the Data-tab field edits it as a string.
|
|
4084
|
+
const: prop2.const == null ? void 0 : String(prop2.const),
|
|
2020
4085
|
required,
|
|
2021
|
-
minLength:
|
|
2022
|
-
maxLength:
|
|
2023
|
-
pattern:
|
|
2024
|
-
format:
|
|
2025
|
-
minimum:
|
|
2026
|
-
maximum:
|
|
2027
|
-
minItems:
|
|
2028
|
-
maxItems:
|
|
4086
|
+
minLength: prop2.minLength,
|
|
4087
|
+
maxLength: prop2.maxLength,
|
|
4088
|
+
pattern: prop2.pattern,
|
|
4089
|
+
format: prop2.format,
|
|
4090
|
+
minimum: prop2.minimum,
|
|
4091
|
+
maximum: prop2.maximum,
|
|
4092
|
+
minItems: prop2.minItems,
|
|
4093
|
+
maxItems: prop2.maxItems,
|
|
4094
|
+
discriminator: prop2.discriminator?.propertyName
|
|
2029
4095
|
};
|
|
2030
4096
|
}
|
|
2031
4097
|
function dataValuesToPatch(values) {
|
|
@@ -2043,6 +4109,7 @@ function dataValuesToPatch(values) {
|
|
|
2043
4109
|
setStr("title", values.title);
|
|
2044
4110
|
setStr("description", values.description);
|
|
2045
4111
|
setStr("default", values.default);
|
|
4112
|
+
setStr("const", values.const);
|
|
2046
4113
|
setStr("format", values.format);
|
|
2047
4114
|
setStr("pattern", values.pattern);
|
|
2048
4115
|
setNum("minLength", values.minLength);
|
|
@@ -2051,28 +4118,31 @@ function dataValuesToPatch(values) {
|
|
|
2051
4118
|
setNum("maximum", values.maximum);
|
|
2052
4119
|
setNum("minItems", values.minItems);
|
|
2053
4120
|
setNum("maxItems", values.maxItems);
|
|
4121
|
+
if (typeof values.discriminator === "string" && values.discriminator.length) {
|
|
4122
|
+
patch.discriminator = { propertyName: values.discriminator };
|
|
4123
|
+
} else {
|
|
4124
|
+
patch.discriminator = void 0;
|
|
4125
|
+
}
|
|
2054
4126
|
return patch;
|
|
2055
4127
|
}
|
|
2056
4128
|
|
|
2057
4129
|
// src/inspector/DataTab.tsx
|
|
2058
|
-
import { jsx as
|
|
4130
|
+
import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2059
4131
|
function DataTab({ node, state }) {
|
|
2060
|
-
const
|
|
2061
|
-
const
|
|
2062
|
-
const
|
|
4132
|
+
const isBranch = node.slotKind === "branch";
|
|
4133
|
+
const type = isBranch ? BRANCH_META_TYPE : node.property.oneOf ? "oneOf" : node.property.type ?? "string";
|
|
4134
|
+
const schema = useMemo8(() => buildDataMetaSchema(type), [type]);
|
|
4135
|
+
const values = useMemo8(
|
|
2063
4136
|
() => propertyToDataValues(node.property, node.required),
|
|
2064
4137
|
[node.property, node.required]
|
|
2065
4138
|
);
|
|
2066
|
-
const prevRef =
|
|
4139
|
+
const prevRef = useRef4(values);
|
|
2067
4140
|
prevRef.current = values;
|
|
2068
4141
|
const handleChange = (data) => {
|
|
2069
4142
|
const next = data;
|
|
2070
4143
|
const prev = prevRef.current;
|
|
2071
|
-
if (next.type !== prev.type) {
|
|
2072
|
-
state.changePropertyType(
|
|
2073
|
-
node.path,
|
|
2074
|
-
next.type
|
|
2075
|
-
);
|
|
4144
|
+
if (!isBranch && next.type !== prev.type) {
|
|
4145
|
+
state.changePropertyType(node.path, next.type);
|
|
2076
4146
|
return;
|
|
2077
4147
|
}
|
|
2078
4148
|
if (next.required !== prev.required) {
|
|
@@ -2081,30 +4151,66 @@ function DataTab({ node, state }) {
|
|
|
2081
4151
|
}
|
|
2082
4152
|
state.updateProperty(node.path, dataValuesToPatch(next));
|
|
2083
4153
|
};
|
|
2084
|
-
return /* @__PURE__ */
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
4154
|
+
return /* @__PURE__ */ jsxs8("div", { className: "space-y-3", children: [
|
|
4155
|
+
!isBranch && /* @__PURE__ */ jsx10(ReferencePicker, { node, state, disabled: state.readOnly }),
|
|
4156
|
+
/* @__PURE__ */ jsx10(
|
|
4157
|
+
JsonSchemaFormRenderer4,
|
|
4158
|
+
{
|
|
4159
|
+
schema,
|
|
4160
|
+
data: values,
|
|
4161
|
+
onChange: handleChange,
|
|
4162
|
+
deferStateUpdates: true,
|
|
4163
|
+
showActions: false,
|
|
4164
|
+
showExtraProperties: false,
|
|
4165
|
+
readOnly: state.readOnly
|
|
4166
|
+
},
|
|
4167
|
+
pathKey(node.path) + ":" + type
|
|
4168
|
+
)
|
|
4169
|
+
] });
|
|
4170
|
+
}
|
|
4171
|
+
function availableDefinitions(state, node) {
|
|
4172
|
+
const defs = state.schema.$defs ?? state.schema.definitions;
|
|
4173
|
+
if (!defs) return [];
|
|
4174
|
+
const selfName = node.path.length === 1 && node.path[0].kind === "def" ? node.path[0].name : void 0;
|
|
4175
|
+
return Object.keys(defs).filter((name) => name !== selfName);
|
|
4176
|
+
}
|
|
4177
|
+
function ReferencePicker({
|
|
4178
|
+
node,
|
|
4179
|
+
state,
|
|
4180
|
+
disabled
|
|
4181
|
+
}) {
|
|
4182
|
+
const names = availableDefinitions(state, node);
|
|
4183
|
+
if (names.length === 0) return null;
|
|
4184
|
+
const isArray = node.property.type === "array";
|
|
4185
|
+
const label = isArray ? "Reference a definition (array items)" : "Reference a definition";
|
|
4186
|
+
const placeholder = isArray ? "Make items reuse a shared definition..." : "Reuse a shared definition...";
|
|
4187
|
+
return /* @__PURE__ */ jsxs8("div", { className: "space-y-1", children: [
|
|
4188
|
+
/* @__PURE__ */ jsx10("label", { className: "text-xs font-medium text-muted-foreground", children: label }),
|
|
4189
|
+
/* @__PURE__ */ jsxs8(
|
|
4190
|
+
Select,
|
|
4191
|
+
{
|
|
4192
|
+
value: "",
|
|
4193
|
+
onValueChange: (name) => state.setReference(node.path, name),
|
|
4194
|
+
disabled,
|
|
4195
|
+
children: [
|
|
4196
|
+
/* @__PURE__ */ jsx10(SelectTrigger, { className: "h-8 text-sm", children: /* @__PURE__ */ jsx10(SelectValue, { placeholder }) }),
|
|
4197
|
+
/* @__PURE__ */ jsx10(SelectContent, { children: names.map((name) => /* @__PURE__ */ jsx10(SelectItem, { value: name, children: name }, name)) })
|
|
4198
|
+
]
|
|
4199
|
+
}
|
|
4200
|
+
)
|
|
4201
|
+
] });
|
|
2096
4202
|
}
|
|
2097
4203
|
|
|
2098
4204
|
// src/inspector/UITab.tsx
|
|
2099
|
-
import { useMemo as
|
|
4205
|
+
import { useMemo as useMemo9 } from "react";
|
|
2100
4206
|
import { JsonSchemaFormRenderer as JsonSchemaFormRenderer5 } from "@petrarca/sonnet-forms";
|
|
2101
|
-
import { JsonEditor as
|
|
4207
|
+
import { JsonEditor as JsonEditor2 } from "@petrarca/sonnet-ui/json-editor";
|
|
2102
4208
|
import {
|
|
2103
4209
|
WIDGET_OPTION_DEFS,
|
|
2104
4210
|
optionDefsToSchema
|
|
2105
4211
|
} from "@petrarca/sonnet-forms/widget-options";
|
|
2106
|
-
import { FormSelect as
|
|
2107
|
-
import { jsx as
|
|
4212
|
+
import { FormSelect as FormSelect4 } from "@petrarca/sonnet-forms";
|
|
4213
|
+
import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2108
4214
|
var PRESENTATION_META_SCHEMA = {
|
|
2109
4215
|
title: "",
|
|
2110
4216
|
type: "object",
|
|
@@ -2167,14 +4273,15 @@ function PresentationEditor({
|
|
|
2167
4273
|
}
|
|
2168
4274
|
onChange(next);
|
|
2169
4275
|
};
|
|
2170
|
-
return /* @__PURE__ */
|
|
2171
|
-
/* @__PURE__ */
|
|
2172
|
-
/* @__PURE__ */
|
|
4276
|
+
return /* @__PURE__ */ jsxs9("div", { children: [
|
|
4277
|
+
/* @__PURE__ */ jsx11("p", { className: "heading-meta mb-2", children: "Presentation (form overrides)" }),
|
|
4278
|
+
/* @__PURE__ */ jsx11(
|
|
2173
4279
|
JsonSchemaFormRenderer5,
|
|
2174
4280
|
{
|
|
2175
4281
|
schema: PRESENTATION_META_SCHEMA,
|
|
2176
4282
|
data: values,
|
|
2177
4283
|
onChange: handleChange,
|
|
4284
|
+
deferStateUpdates: true,
|
|
2178
4285
|
showActions: false,
|
|
2179
4286
|
showExtraProperties: false,
|
|
2180
4287
|
readOnly
|
|
@@ -2191,14 +4298,15 @@ function OptionsEditor({
|
|
|
2191
4298
|
onChange
|
|
2192
4299
|
}) {
|
|
2193
4300
|
if (optionsSchema) {
|
|
2194
|
-
return /* @__PURE__ */
|
|
2195
|
-
/* @__PURE__ */
|
|
2196
|
-
/* @__PURE__ */
|
|
4301
|
+
return /* @__PURE__ */ jsxs9("div", { children: [
|
|
4302
|
+
/* @__PURE__ */ jsx11("p", { className: "heading-meta mb-2", children: "Options" }),
|
|
4303
|
+
/* @__PURE__ */ jsx11(
|
|
2197
4304
|
JsonSchemaFormRenderer5,
|
|
2198
4305
|
{
|
|
2199
4306
|
schema: optionsSchema,
|
|
2200
4307
|
data: currentOptions,
|
|
2201
4308
|
onChange,
|
|
4309
|
+
deferStateUpdates: true,
|
|
2202
4310
|
showActions: false,
|
|
2203
4311
|
showExtraProperties: false,
|
|
2204
4312
|
readOnly
|
|
@@ -2207,10 +4315,10 @@ function OptionsEditor({
|
|
|
2207
4315
|
)
|
|
2208
4316
|
] });
|
|
2209
4317
|
}
|
|
2210
|
-
return /* @__PURE__ */
|
|
2211
|
-
/* @__PURE__ */
|
|
2212
|
-
/* @__PURE__ */
|
|
2213
|
-
|
|
4318
|
+
return /* @__PURE__ */ jsxs9("div", { children: [
|
|
4319
|
+
/* @__PURE__ */ jsx11("p", { className: "heading-meta mb-2", children: "Options (raw JSON)" }),
|
|
4320
|
+
/* @__PURE__ */ jsx11(
|
|
4321
|
+
JsonEditor2,
|
|
2214
4322
|
{
|
|
2215
4323
|
value: JSON.stringify(currentOptions ?? {}, null, 2),
|
|
2216
4324
|
editablePaths: readOnly ? [] : ["/"],
|
|
@@ -2231,17 +4339,17 @@ function UITab({
|
|
|
2231
4339
|
state,
|
|
2232
4340
|
widgetOptions
|
|
2233
4341
|
}) {
|
|
2234
|
-
const
|
|
4342
|
+
const idBase = pathKey(node.path);
|
|
2235
4343
|
const uiEntry = getUiEntryAtPath(state.uiSchema, node.path) ?? {};
|
|
2236
4344
|
const currentWidget = typeof uiEntry["x-ui-widget"] === "string" ? uiEntry["x-ui-widget"] : null;
|
|
2237
4345
|
const currentOptions = uiEntry["x-ui-options"] ?? {};
|
|
2238
4346
|
const rootLayout = state.uiSchema["x-ui-layout"];
|
|
2239
4347
|
const inheritedLabelPlacement = rootLayout?.labelPlacement ?? "top";
|
|
2240
|
-
const widgetChoices =
|
|
4348
|
+
const widgetChoices = useMemo9(
|
|
2241
4349
|
() => compatibleWidgets(node.property),
|
|
2242
4350
|
[node.property]
|
|
2243
4351
|
);
|
|
2244
|
-
const optionsSchema =
|
|
4352
|
+
const optionsSchema = useMemo9(() => {
|
|
2245
4353
|
if (!currentWidget) return null;
|
|
2246
4354
|
const merged = { ...WIDGET_OPTION_DEFS, ...widgetOptions ?? {} };
|
|
2247
4355
|
const descriptors = merged[currentWidget];
|
|
@@ -2274,11 +4382,11 @@ function UITab({
|
|
|
2274
4382
|
else delete next["x-ui-options"];
|
|
2275
4383
|
state.setUiEntry(node.path, next);
|
|
2276
4384
|
};
|
|
2277
|
-
return /* @__PURE__ */
|
|
2278
|
-
/* @__PURE__ */
|
|
2279
|
-
|
|
4385
|
+
return /* @__PURE__ */ jsxs9("div", { className: "space-y-4", children: [
|
|
4386
|
+
/* @__PURE__ */ jsx11(
|
|
4387
|
+
FormSelect4,
|
|
2280
4388
|
{
|
|
2281
|
-
id: `${
|
|
4389
|
+
id: `${idBase}-widget`,
|
|
2282
4390
|
name: "widget",
|
|
2283
4391
|
label: "Widget",
|
|
2284
4392
|
value: currentWidget ?? "__auto__",
|
|
@@ -2291,29 +4399,29 @@ function UITab({
|
|
|
2291
4399
|
clearable: false
|
|
2292
4400
|
}
|
|
2293
4401
|
),
|
|
2294
|
-
currentWidget && /* @__PURE__ */
|
|
4402
|
+
currentWidget && /* @__PURE__ */ jsx11(
|
|
2295
4403
|
OptionsEditor,
|
|
2296
4404
|
{
|
|
2297
|
-
widget: `${
|
|
4405
|
+
widget: `${idBase}:${currentWidget}`,
|
|
2298
4406
|
optionsSchema,
|
|
2299
4407
|
currentOptions,
|
|
2300
4408
|
readOnly: state.readOnly,
|
|
2301
4409
|
onChange: setOptions
|
|
2302
4410
|
}
|
|
2303
4411
|
),
|
|
2304
|
-
/* @__PURE__ */
|
|
4412
|
+
/* @__PURE__ */ jsx11(
|
|
2305
4413
|
PresentationEditor,
|
|
2306
4414
|
{
|
|
2307
|
-
fieldName:
|
|
4415
|
+
fieldName: idBase,
|
|
2308
4416
|
uiEntry,
|
|
2309
4417
|
readOnly: state.readOnly,
|
|
2310
4418
|
onChange: (entry) => state.setUiEntry(node.path, entry)
|
|
2311
4419
|
}
|
|
2312
4420
|
),
|
|
2313
|
-
/* @__PURE__ */
|
|
2314
|
-
|
|
4421
|
+
/* @__PURE__ */ jsx11(
|
|
4422
|
+
FormSelect4,
|
|
2315
4423
|
{
|
|
2316
|
-
id: `${
|
|
4424
|
+
id: `${idBase}-labelPlacement`,
|
|
2317
4425
|
name: "labelPlacement",
|
|
2318
4426
|
label: "Label placement",
|
|
2319
4427
|
value: currentOptions.labelPlacement ?? "__inherit__",
|
|
@@ -2334,25 +4442,25 @@ function UITab({
|
|
|
2334
4442
|
}
|
|
2335
4443
|
|
|
2336
4444
|
// src/inspector/EnumEditor.tsx
|
|
2337
|
-
import { useEffect as
|
|
2338
|
-
import { Input as
|
|
4445
|
+
import { useEffect as useEffect4, useState as useState7 } from "react";
|
|
4446
|
+
import { Input as Input4, IconButton as IconButton3, Button as Button3 } from "@petrarca/sonnet-ui";
|
|
2339
4447
|
import { Plus as Plus3, Trash2 as Trash23 } from "lucide-react";
|
|
2340
|
-
import { jsx as
|
|
4448
|
+
import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
2341
4449
|
function EnumEditor({
|
|
2342
4450
|
node,
|
|
2343
4451
|
state
|
|
2344
4452
|
}) {
|
|
2345
|
-
const [rows, setRows] =
|
|
4453
|
+
const [rows, setRows] = useState7(
|
|
2346
4454
|
() => readEnumChoices(node.property, getUiEntryAtPath(state.uiSchema, node.path))
|
|
2347
4455
|
);
|
|
2348
|
-
|
|
4456
|
+
useEffect4(() => {
|
|
2349
4457
|
setRows(
|
|
2350
4458
|
readEnumChoices(
|
|
2351
4459
|
node.property,
|
|
2352
4460
|
getUiEntryAtPath(state.uiSchema, node.path)
|
|
2353
4461
|
)
|
|
2354
4462
|
);
|
|
2355
|
-
}, [node.path
|
|
4463
|
+
}, [pathKey(node.path)]);
|
|
2356
4464
|
const canEditLabels = true;
|
|
2357
4465
|
const commit = (next) => {
|
|
2358
4466
|
setRows(next);
|
|
@@ -2363,20 +4471,20 @@ function EnumEditor({
|
|
|
2363
4471
|
};
|
|
2364
4472
|
const removeRow = (i) => commit(rows.filter((_, idx) => idx !== i));
|
|
2365
4473
|
const addRow = () => setRows([...rows, { value: "", label: "" }]);
|
|
2366
|
-
return /* @__PURE__ */
|
|
2367
|
-
/* @__PURE__ */
|
|
2368
|
-
/* @__PURE__ */
|
|
2369
|
-
/* @__PURE__ */
|
|
4474
|
+
return /* @__PURE__ */ jsxs10("div", { className: "space-y-3", children: [
|
|
4475
|
+
/* @__PURE__ */ jsxs10("div", { children: [
|
|
4476
|
+
/* @__PURE__ */ jsx12("p", { className: "text-sm font-medium", children: "Allowed values" }),
|
|
4477
|
+
/* @__PURE__ */ jsx12("p", { className: "text-xs text-muted-foreground", children: "Define a fixed set of choices. Leave empty for free input." })
|
|
2370
4478
|
] }),
|
|
2371
|
-
rows.length === 0 ? /* @__PURE__ */
|
|
2372
|
-
canEditLabels && /* @__PURE__ */
|
|
2373
|
-
/* @__PURE__ */
|
|
2374
|
-
/* @__PURE__ */
|
|
2375
|
-
/* @__PURE__ */
|
|
4479
|
+
rows.length === 0 ? /* @__PURE__ */ jsx12("p", { className: "text-xs text-muted-foreground", children: "No choices yet." }) : /* @__PURE__ */ jsxs10("div", { className: "space-y-2", children: [
|
|
4480
|
+
canEditLabels && /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2 px-1", children: [
|
|
4481
|
+
/* @__PURE__ */ jsx12("span", { className: "heading-meta flex-1", children: "Value" }),
|
|
4482
|
+
/* @__PURE__ */ jsx12("span", { className: "heading-meta flex-1", children: "Label (optional)" }),
|
|
4483
|
+
/* @__PURE__ */ jsx12("span", { className: "w-7" })
|
|
2376
4484
|
] }),
|
|
2377
|
-
rows.map((row, i) => /* @__PURE__ */
|
|
2378
|
-
/* @__PURE__ */
|
|
2379
|
-
|
|
4485
|
+
rows.map((row, i) => /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2", children: [
|
|
4486
|
+
/* @__PURE__ */ jsx12(
|
|
4487
|
+
Input4,
|
|
2380
4488
|
{
|
|
2381
4489
|
value: row.value,
|
|
2382
4490
|
onChange: (e) => updateRow(i, { value: e.target.value }),
|
|
@@ -2385,8 +4493,8 @@ function EnumEditor({
|
|
|
2385
4493
|
className: "h-8 text-sm flex-1"
|
|
2386
4494
|
}
|
|
2387
4495
|
),
|
|
2388
|
-
canEditLabels && /* @__PURE__ */
|
|
2389
|
-
|
|
4496
|
+
canEditLabels && /* @__PURE__ */ jsx12(
|
|
4497
|
+
Input4,
|
|
2390
4498
|
{
|
|
2391
4499
|
value: row.label ?? "",
|
|
2392
4500
|
onChange: (e) => updateRow(i, { label: e.target.value }),
|
|
@@ -2395,26 +4503,26 @@ function EnumEditor({
|
|
|
2395
4503
|
className: "h-8 text-sm flex-1"
|
|
2396
4504
|
}
|
|
2397
4505
|
),
|
|
2398
|
-
!state.readOnly && /* @__PURE__ */
|
|
4506
|
+
!state.readOnly && /* @__PURE__ */ jsx12(
|
|
2399
4507
|
IconButton3,
|
|
2400
4508
|
{
|
|
2401
4509
|
label: "Remove value",
|
|
2402
4510
|
tone: "destructive",
|
|
2403
4511
|
onClick: () => removeRow(i),
|
|
2404
|
-
children: /* @__PURE__ */
|
|
4512
|
+
children: /* @__PURE__ */ jsx12(Trash23, {})
|
|
2405
4513
|
}
|
|
2406
4514
|
)
|
|
2407
4515
|
] }, i))
|
|
2408
4516
|
] }),
|
|
2409
|
-
!state.readOnly && /* @__PURE__ */
|
|
2410
|
-
|
|
4517
|
+
!state.readOnly && /* @__PURE__ */ jsxs10(
|
|
4518
|
+
Button3,
|
|
2411
4519
|
{
|
|
2412
4520
|
variant: "outline",
|
|
2413
4521
|
size: "sm",
|
|
2414
4522
|
className: "gap-1.5",
|
|
2415
4523
|
onClick: addRow,
|
|
2416
4524
|
children: [
|
|
2417
|
-
/* @__PURE__ */
|
|
4525
|
+
/* @__PURE__ */ jsx12(Plus3, { className: "h-4 w-4" }),
|
|
2418
4526
|
"Add value"
|
|
2419
4527
|
]
|
|
2420
4528
|
}
|
|
@@ -2423,11 +4531,11 @@ function EnumEditor({
|
|
|
2423
4531
|
}
|
|
2424
4532
|
|
|
2425
4533
|
// src/inspector/TableHintsTab.tsx
|
|
2426
|
-
import { useMemo as
|
|
4534
|
+
import { useMemo as useMemo10 } from "react";
|
|
2427
4535
|
import { CELL_RENDERERS } from "@petrarca/sonnet-ui";
|
|
2428
|
-
import { JsonEditor as
|
|
2429
|
-
import { FormSelect as
|
|
2430
|
-
import { jsx as
|
|
4536
|
+
import { JsonEditor as JsonEditor3 } from "@petrarca/sonnet-ui/json-editor";
|
|
4537
|
+
import { FormSelect as FormSelect5, JsonSchemaFormRenderer as JsonSchemaFormRenderer6 } from "@petrarca/sonnet-forms";
|
|
4538
|
+
import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
2431
4539
|
var CELL_NAMES = Object.keys(CELL_RENDERERS);
|
|
2432
4540
|
var COLUMN_META_SCHEMA = {
|
|
2433
4541
|
title: "",
|
|
@@ -2444,7 +4552,7 @@ function TableHintsTab({
|
|
|
2444
4552
|
state
|
|
2445
4553
|
}) {
|
|
2446
4554
|
const hints = readTableHints(node.property);
|
|
2447
|
-
const cellOptions =
|
|
4555
|
+
const cellOptions = useMemo10(
|
|
2448
4556
|
() => JSON.stringify(hints.cellOptions ?? {}, null, 2),
|
|
2449
4557
|
[hints.cellOptions]
|
|
2450
4558
|
);
|
|
@@ -2467,9 +4575,9 @@ function TableHintsTab({
|
|
|
2467
4575
|
column.minWidth = v.minWidth;
|
|
2468
4576
|
commit({ column });
|
|
2469
4577
|
};
|
|
2470
|
-
return /* @__PURE__ */
|
|
2471
|
-
/* @__PURE__ */
|
|
2472
|
-
|
|
4578
|
+
return /* @__PURE__ */ jsxs11("div", { className: "space-y-4", children: [
|
|
4579
|
+
/* @__PURE__ */ jsx13(
|
|
4580
|
+
FormSelect5,
|
|
2473
4581
|
{
|
|
2474
4582
|
id: `${node.key}-cell`,
|
|
2475
4583
|
name: "cell",
|
|
@@ -2484,25 +4592,26 @@ function TableHintsTab({
|
|
|
2484
4592
|
clearable: false
|
|
2485
4593
|
}
|
|
2486
4594
|
),
|
|
2487
|
-
/* @__PURE__ */
|
|
2488
|
-
/* @__PURE__ */
|
|
2489
|
-
/* @__PURE__ */
|
|
4595
|
+
/* @__PURE__ */ jsxs11("div", { children: [
|
|
4596
|
+
/* @__PURE__ */ jsx13("p", { className: "heading-meta mb-2", children: "Column" }),
|
|
4597
|
+
/* @__PURE__ */ jsx13(
|
|
2490
4598
|
JsonSchemaFormRenderer6,
|
|
2491
4599
|
{
|
|
2492
4600
|
schema: COLUMN_META_SCHEMA,
|
|
2493
4601
|
data: columnValues,
|
|
2494
4602
|
onChange: onColumnChange,
|
|
4603
|
+
deferStateUpdates: true,
|
|
2495
4604
|
showActions: false,
|
|
2496
4605
|
showExtraProperties: false,
|
|
2497
4606
|
readOnly: state.readOnly
|
|
2498
4607
|
},
|
|
2499
|
-
node.path
|
|
4608
|
+
pathKey(node.path)
|
|
2500
4609
|
)
|
|
2501
4610
|
] }),
|
|
2502
|
-
/* @__PURE__ */
|
|
2503
|
-
/* @__PURE__ */
|
|
2504
|
-
/* @__PURE__ */
|
|
2505
|
-
|
|
4611
|
+
/* @__PURE__ */ jsxs11("div", { children: [
|
|
4612
|
+
/* @__PURE__ */ jsx13("p", { className: "heading-meta mb-2", children: "Cell options (JSON)" }),
|
|
4613
|
+
/* @__PURE__ */ jsx13(
|
|
4614
|
+
JsonEditor3,
|
|
2506
4615
|
{
|
|
2507
4616
|
value: cellOptions,
|
|
2508
4617
|
editablePaths: state.readOnly ? [] : ["/"],
|
|
@@ -2522,22 +4631,236 @@ function TableHintsTab({
|
|
|
2522
4631
|
] });
|
|
2523
4632
|
}
|
|
2524
4633
|
|
|
4634
|
+
// src/inspector/ConditionEditor.tsx
|
|
4635
|
+
import { useMemo as useMemo11 } from "react";
|
|
4636
|
+
import { FormSelect as FormSelect6 } from "@petrarca/sonnet-forms";
|
|
4637
|
+
import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
4638
|
+
function siblingFields(state, node) {
|
|
4639
|
+
const objectPath = node.path.slice(0, -1);
|
|
4640
|
+
const owner = objectPath.length === 0 ? { properties: state.schema.properties } : getProperty(state.schema, objectPath);
|
|
4641
|
+
return owner?.properties ?? {};
|
|
4642
|
+
}
|
|
4643
|
+
function ConditionEditor({
|
|
4644
|
+
node,
|
|
4645
|
+
state
|
|
4646
|
+
}) {
|
|
4647
|
+
const condition2 = useMemo11(
|
|
4648
|
+
() => ifToCondition(node.property.if),
|
|
4649
|
+
[node.property.if]
|
|
4650
|
+
);
|
|
4651
|
+
const fieldProps = useMemo11(() => siblingFields(state, node), [state, node]);
|
|
4652
|
+
const fields = useMemo11(() => Object.keys(fieldProps), [fieldProps]);
|
|
4653
|
+
const disabled = state.readOnly;
|
|
4654
|
+
const lastSeg = node.path[node.path.length - 1];
|
|
4655
|
+
const combinator = lastSeg?.kind === "condition" ? lastSeg.combinator : "allOf";
|
|
4656
|
+
const objectPath = node.path.slice(0, -1);
|
|
4657
|
+
const commit = (next) => {
|
|
4658
|
+
const ifSchema = conditionToIf(next, fieldProps[next.field]?.type);
|
|
4659
|
+
if (ifSchema) state.setCondition(node.path, ifSchema);
|
|
4660
|
+
};
|
|
4661
|
+
const fieldOptions = fields.map((f) => ({ value: f, label: f }));
|
|
4662
|
+
const needsValue = condition2.operator !== "exists";
|
|
4663
|
+
return /* @__PURE__ */ jsxs12("div", { className: "space-y-3", children: [
|
|
4664
|
+
/* @__PURE__ */ jsxs12("p", { className: "text-xs text-muted-foreground", children: [
|
|
4665
|
+
"Show the ",
|
|
4666
|
+
/* @__PURE__ */ jsx14("span", { className: "font-medium", children: "then" }),
|
|
4667
|
+
" fields when this condition holds (otherwise the ",
|
|
4668
|
+
/* @__PURE__ */ jsx14("span", { className: "font-medium", children: "else" }),
|
|
4669
|
+
" ",
|
|
4670
|
+
"fields, if any)."
|
|
4671
|
+
] }),
|
|
4672
|
+
/* @__PURE__ */ jsx14(
|
|
4673
|
+
FormSelect6,
|
|
4674
|
+
{
|
|
4675
|
+
id: "condition-match",
|
|
4676
|
+
name: "match",
|
|
4677
|
+
label: "Match (all conditions on this object)",
|
|
4678
|
+
options: [
|
|
4679
|
+
{ value: "allOf", label: "All (AND)" },
|
|
4680
|
+
{ value: "anyOf", label: "Any (OR)" }
|
|
4681
|
+
],
|
|
4682
|
+
value: combinator,
|
|
4683
|
+
onChange: (v) => state.setConditionalBehavior(
|
|
4684
|
+
objectPath,
|
|
4685
|
+
v === "anyOf" ? "anyOf" : "allOf"
|
|
4686
|
+
),
|
|
4687
|
+
clearable: false,
|
|
4688
|
+
disabled
|
|
4689
|
+
}
|
|
4690
|
+
),
|
|
4691
|
+
/* @__PURE__ */ jsx14(
|
|
4692
|
+
FormSelect6,
|
|
4693
|
+
{
|
|
4694
|
+
id: "condition-field",
|
|
4695
|
+
name: "field",
|
|
4696
|
+
label: "When property",
|
|
4697
|
+
options: fieldOptions,
|
|
4698
|
+
value: condition2.field || null,
|
|
4699
|
+
onChange: (v) => commit({ ...condition2, field: v ?? "" }),
|
|
4700
|
+
placeholder: "Select a property...",
|
|
4701
|
+
clearable: false,
|
|
4702
|
+
disabled
|
|
4703
|
+
}
|
|
4704
|
+
),
|
|
4705
|
+
/* @__PURE__ */ jsx14(
|
|
4706
|
+
FormSelect6,
|
|
4707
|
+
{
|
|
4708
|
+
id: "condition-operator",
|
|
4709
|
+
name: "operator",
|
|
4710
|
+
label: "Operator",
|
|
4711
|
+
options: OPERATOR_OPTIONS,
|
|
4712
|
+
value: condition2.operator,
|
|
4713
|
+
onChange: (v) => commit({ ...condition2, operator: v ?? "=" }),
|
|
4714
|
+
clearable: false,
|
|
4715
|
+
disabled
|
|
4716
|
+
}
|
|
4717
|
+
),
|
|
4718
|
+
needsValue && /* @__PURE__ */ jsx14(
|
|
4719
|
+
OperatorValueInput,
|
|
4720
|
+
{
|
|
4721
|
+
id: "condition-value",
|
|
4722
|
+
cacheKey: `${node.path.length}:${condition2.field}:${condition2.operator}`,
|
|
4723
|
+
value: condition2.value,
|
|
4724
|
+
onCommit: (v) => commit({ ...condition2, value: v }),
|
|
4725
|
+
disabled: disabled || !condition2.field
|
|
4726
|
+
}
|
|
4727
|
+
)
|
|
4728
|
+
] });
|
|
4729
|
+
}
|
|
4730
|
+
|
|
4731
|
+
// src/inspector/RuleEditor.tsx
|
|
4732
|
+
import { useMemo as useMemo12 } from "react";
|
|
4733
|
+
import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
4734
|
+
function siblingPointers(state, node) {
|
|
4735
|
+
const parentProps = node.path.length <= 1 ? state.schema.properties : findParentProperties(state, node);
|
|
4736
|
+
const self = node.key;
|
|
4737
|
+
return Object.entries(parentProps ?? {}).filter(([k]) => k !== self).map(([k, prop2]) => ({ pointer: `/${k}`, type: prop2.type }));
|
|
4738
|
+
}
|
|
4739
|
+
function findParentProperties(state, node) {
|
|
4740
|
+
let props = state.schema.properties;
|
|
4741
|
+
for (let i = 0; i < node.path.length - 1; i++) {
|
|
4742
|
+
const seg = node.path[i];
|
|
4743
|
+
if (seg.kind !== "prop" || !props) return void 0;
|
|
4744
|
+
const next = props[seg.key];
|
|
4745
|
+
props = next?.type === "array" ? next.items?.properties : next?.properties;
|
|
4746
|
+
if (!props) return void 0;
|
|
4747
|
+
}
|
|
4748
|
+
return props;
|
|
4749
|
+
}
|
|
4750
|
+
function RuleEditor({
|
|
4751
|
+
node,
|
|
4752
|
+
state
|
|
4753
|
+
}) {
|
|
4754
|
+
const scopes = useMemo12(() => siblingPointers(state, node), [state, node]);
|
|
4755
|
+
return /* @__PURE__ */ jsxs13("div", { className: "space-y-3", children: [
|
|
4756
|
+
/* @__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." }),
|
|
4757
|
+
/* @__PURE__ */ jsx15(
|
|
4758
|
+
RuleFields,
|
|
4759
|
+
{
|
|
4760
|
+
rule: node.property["x-ui-rule"],
|
|
4761
|
+
scopes,
|
|
4762
|
+
effects: FIELD_EFFECT_OPTIONS,
|
|
4763
|
+
idPrefix: "rule",
|
|
4764
|
+
disabled: state.readOnly,
|
|
4765
|
+
onChange: (rule) => state.updateProperty(node.path, { "x-ui-rule": rule })
|
|
4766
|
+
}
|
|
4767
|
+
)
|
|
4768
|
+
] });
|
|
4769
|
+
}
|
|
4770
|
+
|
|
4771
|
+
// src/inspector/ReferenceTab.tsx
|
|
4772
|
+
import { useMemo as useMemo13, useRef as useRef5 } from "react";
|
|
4773
|
+
import { JsonSchemaFormRenderer as JsonSchemaFormRenderer7 } from "@petrarca/sonnet-forms";
|
|
4774
|
+
import { Button as Button4 } from "@petrarca/sonnet-ui";
|
|
4775
|
+
import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
4776
|
+
function toOverrides(prop2, required) {
|
|
4777
|
+
return {
|
|
4778
|
+
title: prop2.title,
|
|
4779
|
+
description: prop2.description,
|
|
4780
|
+
// Coerce to string for the text field; non-string defaults (number, boolean)
|
|
4781
|
+
// are serialised and written back as strings -- v1 limitation.
|
|
4782
|
+
default: prop2.default == null ? void 0 : String(prop2.default),
|
|
4783
|
+
required
|
|
4784
|
+
};
|
|
4785
|
+
}
|
|
4786
|
+
function toPatch(values) {
|
|
4787
|
+
const str = (v) => typeof v === "string" && v.length > 0 ? v : void 0;
|
|
4788
|
+
return {
|
|
4789
|
+
title: str(values.title),
|
|
4790
|
+
description: str(values.description),
|
|
4791
|
+
default: str(values.default)
|
|
4792
|
+
};
|
|
4793
|
+
}
|
|
4794
|
+
function ReferenceTab({
|
|
4795
|
+
node,
|
|
4796
|
+
state
|
|
4797
|
+
}) {
|
|
4798
|
+
const schema = useMemo13(() => buildReferenceMetaSchema(), []);
|
|
4799
|
+
const values = useMemo13(
|
|
4800
|
+
() => toOverrides(node.property, node.required),
|
|
4801
|
+
[node.property, node.required]
|
|
4802
|
+
);
|
|
4803
|
+
const prevRef = useRef5(values);
|
|
4804
|
+
prevRef.current = values;
|
|
4805
|
+
const handleChange = (data) => {
|
|
4806
|
+
const next = data;
|
|
4807
|
+
const prev = prevRef.current;
|
|
4808
|
+
if (next.required !== prev.required) {
|
|
4809
|
+
state.setRequired(node.path, !!next.required);
|
|
4810
|
+
return;
|
|
4811
|
+
}
|
|
4812
|
+
state.updateProperty(node.path, toPatch(next));
|
|
4813
|
+
};
|
|
4814
|
+
return /* @__PURE__ */ jsxs14("div", { className: "space-y-4", children: [
|
|
4815
|
+
/* @__PURE__ */ jsxs14("p", { className: "text-sm text-muted-foreground", children: [
|
|
4816
|
+
"References ",
|
|
4817
|
+
/* @__PURE__ */ jsx16("span", { className: "font-medium", children: node.reference }),
|
|
4818
|
+
". 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."
|
|
4819
|
+
] }),
|
|
4820
|
+
/* @__PURE__ */ jsx16(
|
|
4821
|
+
JsonSchemaFormRenderer7,
|
|
4822
|
+
{
|
|
4823
|
+
schema,
|
|
4824
|
+
data: values,
|
|
4825
|
+
onChange: handleChange,
|
|
4826
|
+
deferStateUpdates: true,
|
|
4827
|
+
showActions: false,
|
|
4828
|
+
showExtraProperties: false,
|
|
4829
|
+
readOnly: state.readOnly
|
|
4830
|
+
},
|
|
4831
|
+
pathKey(node.path)
|
|
4832
|
+
),
|
|
4833
|
+
!state.readOnly && state.canEditProperty(node.path) && /* @__PURE__ */ jsxs14("div", { className: "border-t pt-3", children: [
|
|
4834
|
+
/* @__PURE__ */ jsx16(
|
|
4835
|
+
Button4,
|
|
4836
|
+
{
|
|
4837
|
+
variant: "outline",
|
|
4838
|
+
size: "sm",
|
|
4839
|
+
onClick: () => state.changePropertyType(node.path, "string"),
|
|
4840
|
+
children: "Stop referencing"
|
|
4841
|
+
}
|
|
4842
|
+
),
|
|
4843
|
+
/* @__PURE__ */ jsx16("p", { className: "mt-1 text-xs text-muted-foreground", children: "Removes the reference and turns this into an editable property." })
|
|
4844
|
+
] })
|
|
4845
|
+
] });
|
|
4846
|
+
}
|
|
4847
|
+
|
|
2525
4848
|
// src/inspector/Inspector.tsx
|
|
2526
|
-
import { jsx as
|
|
4849
|
+
import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
2527
4850
|
var ENUM_CAPABLE_TYPES = /* @__PURE__ */ new Set(["string", "number", "integer"]);
|
|
2528
4851
|
function KeyField({
|
|
2529
4852
|
fieldKey,
|
|
2530
4853
|
onRename,
|
|
2531
4854
|
readOnly
|
|
2532
4855
|
}) {
|
|
2533
|
-
const [editing, setEditing] =
|
|
2534
|
-
const [value, setValue] =
|
|
2535
|
-
const ref =
|
|
2536
|
-
|
|
4856
|
+
const [editing, setEditing] = useState8(false);
|
|
4857
|
+
const [value, setValue] = useState8(fieldKey);
|
|
4858
|
+
const ref = useRef6(null);
|
|
4859
|
+
useEffect5(() => {
|
|
2537
4860
|
setValue(fieldKey);
|
|
2538
4861
|
setEditing(false);
|
|
2539
4862
|
}, [fieldKey]);
|
|
2540
|
-
|
|
4863
|
+
useEffect5(() => {
|
|
2541
4864
|
if (editing) {
|
|
2542
4865
|
ref.current?.focus();
|
|
2543
4866
|
ref.current?.select();
|
|
@@ -2553,14 +4876,14 @@ function KeyField({
|
|
|
2553
4876
|
setEditing(false);
|
|
2554
4877
|
};
|
|
2555
4878
|
if (!editing) {
|
|
2556
|
-
return /* @__PURE__ */
|
|
2557
|
-
/* @__PURE__ */
|
|
2558
|
-
!readOnly && /* @__PURE__ */
|
|
4879
|
+
return /* @__PURE__ */ jsxs15("div", { className: "flex items-center gap-1", children: [
|
|
4880
|
+
/* @__PURE__ */ jsx17("span", { className: "text-sm font-semibold truncate", children: fieldKey }),
|
|
4881
|
+
!readOnly && /* @__PURE__ */ jsx17(IconButton4, { label: "Rename property", onClick: () => setEditing(true), children: /* @__PURE__ */ jsx17(Pencil, {}) })
|
|
2559
4882
|
] });
|
|
2560
4883
|
}
|
|
2561
|
-
return /* @__PURE__ */
|
|
2562
|
-
/* @__PURE__ */
|
|
2563
|
-
|
|
4884
|
+
return /* @__PURE__ */ jsxs15("div", { className: "flex items-center gap-1", children: [
|
|
4885
|
+
/* @__PURE__ */ jsx17(
|
|
4886
|
+
Input5,
|
|
2564
4887
|
{
|
|
2565
4888
|
ref,
|
|
2566
4889
|
value,
|
|
@@ -2577,8 +4900,8 @@ function KeyField({
|
|
|
2577
4900
|
className: "h-7 text-sm font-medium"
|
|
2578
4901
|
}
|
|
2579
4902
|
),
|
|
2580
|
-
/* @__PURE__ */
|
|
2581
|
-
/* @__PURE__ */
|
|
4903
|
+
/* @__PURE__ */ jsx17(IconButton4, { label: "Confirm rename", onClick: commit, children: /* @__PURE__ */ jsx17(Check2, {}) }),
|
|
4904
|
+
/* @__PURE__ */ jsx17(IconButton4, { label: "Cancel rename", onClick: cancel, children: /* @__PURE__ */ jsx17(X, {}) })
|
|
2582
4905
|
] });
|
|
2583
4906
|
}
|
|
2584
4907
|
function findNode(nodes, path) {
|
|
@@ -2589,49 +4912,109 @@ function findNode(nodes, path) {
|
|
|
2589
4912
|
}
|
|
2590
4913
|
return void 0;
|
|
2591
4914
|
}
|
|
4915
|
+
function InspectorHeader({
|
|
4916
|
+
node,
|
|
4917
|
+
state
|
|
4918
|
+
}) {
|
|
4919
|
+
const isDefinition = node.kind === "definition";
|
|
4920
|
+
const noRename = isDefinition ? state.capabilities.locked : state.readOnly || node.slotKind === "branch" || node.slotKind === "conditional";
|
|
4921
|
+
const onRename = (next) => isDefinition ? state.renameDefinition(node.key, next) : state.renameProperty(node.path, next);
|
|
4922
|
+
return /* @__PURE__ */ jsxs15("div", { className: "mb-3 space-y-1", children: [
|
|
4923
|
+
/* @__PURE__ */ jsx17(
|
|
4924
|
+
KeyField,
|
|
4925
|
+
{
|
|
4926
|
+
fieldKey: node.key,
|
|
4927
|
+
readOnly: noRename,
|
|
4928
|
+
onRename
|
|
4929
|
+
},
|
|
4930
|
+
pathKey(node.path)
|
|
4931
|
+
),
|
|
4932
|
+
/* @__PURE__ */ jsx17("p", { className: "font-mono text-xs text-muted-foreground", children: isDefinition ? "definition" : node.typeLabel })
|
|
4933
|
+
] });
|
|
4934
|
+
}
|
|
4935
|
+
function PropertyTabs({
|
|
4936
|
+
node,
|
|
4937
|
+
state,
|
|
4938
|
+
widgetOptions
|
|
4939
|
+
}) {
|
|
4940
|
+
const enumCapable = ENUM_CAPABLE_TYPES.has(node.property.type ?? "");
|
|
4941
|
+
const ruleUnsupported = node.path.some(
|
|
4942
|
+
(s) => s.kind === "branch" || s.kind === "condition" || s.kind === "slot"
|
|
4943
|
+
);
|
|
4944
|
+
const uiUnsupported = node.path.some((s) => s.kind !== "prop");
|
|
4945
|
+
return /* @__PURE__ */ jsxs15(Tabs2, { defaultValue: "data", children: [
|
|
4946
|
+
/* @__PURE__ */ jsxs15(TabsList2, { className: "mb-3", children: [
|
|
4947
|
+
/* @__PURE__ */ jsx17(TabsTrigger2, { value: "data", children: "Data" }),
|
|
4948
|
+
enumCapable && /* @__PURE__ */ jsx17(TabsTrigger2, { value: "enum", children: "Enum" }),
|
|
4949
|
+
/* @__PURE__ */ jsx17(TabsTrigger2, { value: "ui", children: "UI" }),
|
|
4950
|
+
!ruleUnsupported && /* @__PURE__ */ jsx17(TabsTrigger2, { value: "rule", children: "Rule" }),
|
|
4951
|
+
/* @__PURE__ */ jsx17(TabsTrigger2, { value: "table", children: "Table" })
|
|
4952
|
+
] }),
|
|
4953
|
+
/* @__PURE__ */ jsx17(TabsContent2, { value: "data", children: /* @__PURE__ */ jsx17(DataTab, { node, state }) }),
|
|
4954
|
+
enumCapable && /* @__PURE__ */ jsx17(TabsContent2, { value: "enum", children: /* @__PURE__ */ jsx17(EnumEditor, { node, state }) }),
|
|
4955
|
+
!ruleUnsupported && /* @__PURE__ */ jsx17(TabsContent2, { value: "rule", children: /* @__PURE__ */ jsx17(RuleEditor, { node, state }) }),
|
|
4956
|
+
/* @__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 }) }),
|
|
4957
|
+
/* @__PURE__ */ jsx17(TabsContent2, { value: "table", children: /* @__PURE__ */ jsx17(TableHintsTab, { node, state }) })
|
|
4958
|
+
] }, `${pathKey(node.path)}:${enumCapable}`);
|
|
4959
|
+
}
|
|
4960
|
+
function InspectorBody({
|
|
4961
|
+
node,
|
|
4962
|
+
state,
|
|
4963
|
+
widgetOptions
|
|
4964
|
+
}) {
|
|
4965
|
+
const lastSeg = node.path[node.path.length - 1];
|
|
4966
|
+
if (node.kind === "reference") {
|
|
4967
|
+
return /* @__PURE__ */ jsx17(ReferenceTab, { node, state });
|
|
4968
|
+
}
|
|
4969
|
+
if (node.readOnly) {
|
|
4970
|
+
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." });
|
|
4971
|
+
}
|
|
4972
|
+
if (node.kind === "definition") {
|
|
4973
|
+
return /* @__PURE__ */ jsxs15("p", { className: "text-sm text-muted-foreground p-2", children: [
|
|
4974
|
+
"Reusable definition ",
|
|
4975
|
+
/* @__PURE__ */ jsx17("span", { className: "font-medium", children: node.key }),
|
|
4976
|
+
". Add or edit its fields in the tree; reference it from a property to reuse its shape. Renaming it here updates every reference."
|
|
4977
|
+
] });
|
|
4978
|
+
}
|
|
4979
|
+
if (node.kind === "conditional") {
|
|
4980
|
+
return /* @__PURE__ */ jsx17(ConditionEditor, { node, state });
|
|
4981
|
+
}
|
|
4982
|
+
if (node.slotKind === "conditional" && lastSeg?.kind === "slot") {
|
|
4983
|
+
return /* @__PURE__ */ jsxs15("p", { className: "text-sm text-muted-foreground p-2", children: [
|
|
4984
|
+
"Add fields to this ",
|
|
4985
|
+
/* @__PURE__ */ jsx17("span", { className: "font-medium", children: node.key }),
|
|
4986
|
+
" ",
|
|
4987
|
+
"branch from the tree. They apply when the condition",
|
|
4988
|
+
" ",
|
|
4989
|
+
node.key === "then" ? "holds" : "does not hold",
|
|
4990
|
+
"."
|
|
4991
|
+
] });
|
|
4992
|
+
}
|
|
4993
|
+
return /* @__PURE__ */ jsx17(PropertyTabs, { node, state, widgetOptions });
|
|
4994
|
+
}
|
|
2592
4995
|
function Inspector({
|
|
2593
4996
|
state,
|
|
2594
4997
|
widgetOptions
|
|
2595
4998
|
}) {
|
|
2596
|
-
const tree =
|
|
4999
|
+
const tree = useMemo14(
|
|
5000
|
+
() => buildPropertyTree(state.schema, state.refResolver),
|
|
5001
|
+
[state.schema, state.refResolver]
|
|
5002
|
+
);
|
|
2597
5003
|
const node = state.selection ? findNode(tree, state.selection) : void 0;
|
|
2598
5004
|
if (!node) {
|
|
2599
|
-
return /* @__PURE__ */
|
|
5005
|
+
return /* @__PURE__ */ jsx17("p", { className: "text-sm text-muted-foreground", children: "Select a property to edit." });
|
|
2600
5006
|
}
|
|
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}`)
|
|
5007
|
+
return /* @__PURE__ */ jsxs15("div", { children: [
|
|
5008
|
+
/* @__PURE__ */ jsx17(InspectorHeader, { node, state }),
|
|
5009
|
+
/* @__PURE__ */ jsx17(InspectorBody, { node, state, widgetOptions })
|
|
2627
5010
|
] });
|
|
2628
5011
|
}
|
|
2629
5012
|
|
|
2630
5013
|
// src/SchemaDesigner.tsx
|
|
2631
|
-
import { Fragment as
|
|
5014
|
+
import { Fragment as Fragment5, jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
2632
5015
|
function SchemaDesigner(props) {
|
|
2633
|
-
const [capabilities, setCapabilities] =
|
|
2634
|
-
|
|
5016
|
+
const [capabilities, setCapabilities] = useState9(props.capabilities);
|
|
5017
|
+
useEffect6(() => {
|
|
2635
5018
|
setCapabilities(props.capabilities);
|
|
2636
5019
|
}, [props.capabilities]);
|
|
2637
5020
|
const state = useDesignerState({ ...props, capabilities });
|
|
@@ -2657,17 +5040,17 @@ function SchemaDesigner(props) {
|
|
|
2657
5040
|
maxWidth: 520,
|
|
2658
5041
|
direction: "right-edge"
|
|
2659
5042
|
});
|
|
2660
|
-
const [inspectorCollapsed, setInspectorCollapsed] =
|
|
2661
|
-
const [treeCollapsed, setTreeCollapsed] =
|
|
2662
|
-
const [settingsOpen, setSettingsOpen] =
|
|
2663
|
-
return /* @__PURE__ */
|
|
2664
|
-
!treeCollapsed && /* @__PURE__ */
|
|
2665
|
-
/* @__PURE__ */
|
|
5043
|
+
const [inspectorCollapsed, setInspectorCollapsed] = useState9(false);
|
|
5044
|
+
const [treeCollapsed, setTreeCollapsed] = useState9(false);
|
|
5045
|
+
const [settingsOpen, setSettingsOpen] = useState9(false);
|
|
5046
|
+
return /* @__PURE__ */ jsxs16("div", { className: "flex w-full h-full overflow-hidden border rounded-lg bg-background", children: [
|
|
5047
|
+
!treeCollapsed && /* @__PURE__ */ jsxs16(Fragment5, { children: [
|
|
5048
|
+
/* @__PURE__ */ jsx18(
|
|
2666
5049
|
"div",
|
|
2667
5050
|
{
|
|
2668
5051
|
style: state.capabilities.showPreview ? { width: treeWidth } : void 0,
|
|
2669
|
-
className: state.capabilities.showPreview ? "shrink-0 overflow-
|
|
2670
|
-
children: /* @__PURE__ */
|
|
5052
|
+
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",
|
|
5053
|
+
children: /* @__PURE__ */ jsx18(
|
|
2671
5054
|
PropertyTree,
|
|
2672
5055
|
{
|
|
2673
5056
|
state,
|
|
@@ -2676,7 +5059,7 @@ function SchemaDesigner(props) {
|
|
|
2676
5059
|
)
|
|
2677
5060
|
}
|
|
2678
5061
|
),
|
|
2679
|
-
state.capabilities.showPreview && /* @__PURE__ */
|
|
5062
|
+
state.capabilities.showPreview && /* @__PURE__ */ jsx18(
|
|
2680
5063
|
ResizeHandle,
|
|
2681
5064
|
{
|
|
2682
5065
|
orientation: "vertical",
|
|
@@ -2686,15 +5069,15 @@ function SchemaDesigner(props) {
|
|
|
2686
5069
|
}
|
|
2687
5070
|
)
|
|
2688
5071
|
] }),
|
|
2689
|
-
treeCollapsed && /* @__PURE__ */
|
|
5072
|
+
treeCollapsed && /* @__PURE__ */ jsx18("div", { className: "shrink-0 border-r flex flex-col items-center gap-1 p-1", children: /* @__PURE__ */ jsx18(
|
|
2690
5073
|
IconButton5,
|
|
2691
5074
|
{
|
|
2692
5075
|
label: "Expand properties",
|
|
2693
5076
|
onClick: () => setTreeCollapsed(false),
|
|
2694
|
-
children: /* @__PURE__ */
|
|
5077
|
+
children: /* @__PURE__ */ jsx18(PanelLeftOpen, {})
|
|
2695
5078
|
}
|
|
2696
5079
|
) }),
|
|
2697
|
-
state.capabilities.showPreview && /* @__PURE__ */
|
|
5080
|
+
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
5081
|
PreviewPane,
|
|
2699
5082
|
{
|
|
2700
5083
|
state,
|
|
@@ -2702,8 +5085,8 @@ function SchemaDesigner(props) {
|
|
|
2702
5085
|
onCapabilitiesChange: setCapabilities
|
|
2703
5086
|
}
|
|
2704
5087
|
) }),
|
|
2705
|
-
!inspectorCollapsed && /* @__PURE__ */
|
|
2706
|
-
/* @__PURE__ */
|
|
5088
|
+
!inspectorCollapsed && /* @__PURE__ */ jsxs16(Fragment5, { children: [
|
|
5089
|
+
/* @__PURE__ */ jsx18(
|
|
2707
5090
|
ResizeHandle,
|
|
2708
5091
|
{
|
|
2709
5092
|
orientation: "vertical",
|
|
@@ -2712,57 +5095,57 @@ function SchemaDesigner(props) {
|
|
|
2712
5095
|
onDoubleClick: resetInspectorWidth
|
|
2713
5096
|
}
|
|
2714
5097
|
),
|
|
2715
|
-
/* @__PURE__ */
|
|
5098
|
+
/* @__PURE__ */ jsxs16(
|
|
2716
5099
|
"div",
|
|
2717
5100
|
{
|
|
2718
5101
|
style: { width: inspectorWidth },
|
|
2719
5102
|
className: "shrink-0 border-l overflow-auto p-4 bg-card",
|
|
2720
5103
|
children: [
|
|
2721
|
-
/* @__PURE__ */
|
|
2722
|
-
/* @__PURE__ */
|
|
2723
|
-
/* @__PURE__ */
|
|
2724
|
-
/* @__PURE__ */
|
|
5104
|
+
/* @__PURE__ */ jsxs16("div", { className: "flex items-center gap-1 mb-2", children: [
|
|
5105
|
+
/* @__PURE__ */ jsx18("p", { className: "heading-meta", children: "Inspector" }),
|
|
5106
|
+
/* @__PURE__ */ jsxs16("span", { className: "ml-auto flex gap-0.5", children: [
|
|
5107
|
+
/* @__PURE__ */ jsx18(
|
|
2725
5108
|
IconButton5,
|
|
2726
5109
|
{
|
|
2727
5110
|
label: "Form settings",
|
|
2728
5111
|
onClick: () => setSettingsOpen(true),
|
|
2729
|
-
children: /* @__PURE__ */
|
|
5112
|
+
children: /* @__PURE__ */ jsx18(Settings, {})
|
|
2730
5113
|
}
|
|
2731
5114
|
),
|
|
2732
|
-
/* @__PURE__ */
|
|
5115
|
+
/* @__PURE__ */ jsx18(
|
|
2733
5116
|
IconButton5,
|
|
2734
5117
|
{
|
|
2735
5118
|
label: "Collapse inspector",
|
|
2736
5119
|
onClick: () => setInspectorCollapsed(true),
|
|
2737
|
-
children: /* @__PURE__ */
|
|
5120
|
+
children: /* @__PURE__ */ jsx18(PanelRightClose, {})
|
|
2738
5121
|
}
|
|
2739
5122
|
)
|
|
2740
5123
|
] })
|
|
2741
5124
|
] }),
|
|
2742
|
-
/* @__PURE__ */
|
|
5125
|
+
/* @__PURE__ */ jsx18(Inspector, { state, widgetOptions: props.widgetOptions })
|
|
2743
5126
|
]
|
|
2744
5127
|
}
|
|
2745
5128
|
)
|
|
2746
5129
|
] }),
|
|
2747
|
-
inspectorCollapsed && /* @__PURE__ */
|
|
2748
|
-
/* @__PURE__ */
|
|
5130
|
+
inspectorCollapsed && /* @__PURE__ */ jsxs16("div", { className: "shrink-0 border-l flex flex-col items-center gap-1 p-1", children: [
|
|
5131
|
+
/* @__PURE__ */ jsx18(
|
|
2749
5132
|
IconButton5,
|
|
2750
5133
|
{
|
|
2751
5134
|
label: "Expand inspector",
|
|
2752
5135
|
onClick: () => setInspectorCollapsed(false),
|
|
2753
|
-
children: /* @__PURE__ */
|
|
5136
|
+
children: /* @__PURE__ */ jsx18(PanelRightOpen, {})
|
|
2754
5137
|
}
|
|
2755
5138
|
),
|
|
2756
|
-
/* @__PURE__ */
|
|
5139
|
+
/* @__PURE__ */ jsx18(
|
|
2757
5140
|
IconButton5,
|
|
2758
5141
|
{
|
|
2759
5142
|
label: "Form settings",
|
|
2760
5143
|
onClick: () => setSettingsOpen(true),
|
|
2761
|
-
children: /* @__PURE__ */
|
|
5144
|
+
children: /* @__PURE__ */ jsx18(Settings, {})
|
|
2762
5145
|
}
|
|
2763
5146
|
)
|
|
2764
5147
|
] }),
|
|
2765
|
-
/* @__PURE__ */
|
|
5148
|
+
/* @__PURE__ */ jsx18(
|
|
2766
5149
|
FormSettingsSheet,
|
|
2767
5150
|
{
|
|
2768
5151
|
open: settingsOpen,
|
|
@@ -2784,41 +5167,77 @@ export {
|
|
|
2784
5167
|
TableHintsTab,
|
|
2785
5168
|
TablePreview,
|
|
2786
5169
|
UITab,
|
|
5170
|
+
addBranch,
|
|
5171
|
+
addConditional,
|
|
5172
|
+
addDefinition,
|
|
5173
|
+
addElse,
|
|
5174
|
+
addLayoutGroup,
|
|
2787
5175
|
addProperty,
|
|
5176
|
+
branch,
|
|
2788
5177
|
buildPropertyTree,
|
|
2789
5178
|
capabilitiesSchema,
|
|
2790
5179
|
changePropertyType,
|
|
2791
5180
|
childContainer,
|
|
2792
|
-
|
|
5181
|
+
childSlots,
|
|
2793
5182
|
compatibleWidgets,
|
|
5183
|
+
condition,
|
|
5184
|
+
def,
|
|
5185
|
+
definitionUsageCount,
|
|
5186
|
+
definitionsOf,
|
|
2794
5187
|
deleteProperty,
|
|
2795
5188
|
embedUiSchema,
|
|
2796
5189
|
getProperty,
|
|
2797
5190
|
getScopedLayout,
|
|
2798
5191
|
getUiEntryAtPath,
|
|
2799
5192
|
isContainer,
|
|
5193
|
+
isReservedKey,
|
|
2800
5194
|
layoutScopes,
|
|
5195
|
+
moveBranch,
|
|
5196
|
+
moveLayoutGroup,
|
|
2801
5197
|
moveProperty,
|
|
5198
|
+
newBranch,
|
|
5199
|
+
newCondition,
|
|
2802
5200
|
newProperty,
|
|
2803
5201
|
orderedKeys,
|
|
5202
|
+
parsePathKey,
|
|
2804
5203
|
pathKey,
|
|
5204
|
+
placeFieldInGroup,
|
|
5205
|
+
prop,
|
|
2805
5206
|
propertyKind,
|
|
2806
5207
|
readEnumChoices,
|
|
2807
5208
|
readTableHints,
|
|
5209
|
+
removeBranch,
|
|
5210
|
+
removeConditional,
|
|
5211
|
+
removeDefinition,
|
|
5212
|
+
removeElse,
|
|
5213
|
+
removeLayoutGroup,
|
|
5214
|
+
renameDefinition,
|
|
5215
|
+
renameLayoutGroup,
|
|
2808
5216
|
renameProperty,
|
|
2809
5217
|
resolveCapabilities,
|
|
2810
5218
|
samePath,
|
|
5219
|
+
sameSegment,
|
|
2811
5220
|
sampleRows,
|
|
5221
|
+
schemaAsRootProp,
|
|
5222
|
+
setCondition,
|
|
5223
|
+
setConditionalBehavior,
|
|
2812
5224
|
setEnum,
|
|
2813
5225
|
setGlobalOptions,
|
|
5226
|
+
setLayoutGroupRule,
|
|
2814
5227
|
setPropertyLayout,
|
|
5228
|
+
setReference,
|
|
2815
5229
|
setRequired,
|
|
2816
5230
|
setRootLayout,
|
|
2817
5231
|
setRootLayoutRows,
|
|
5232
|
+
setScopedGroupRows,
|
|
5233
|
+
setScopedLayoutGroups,
|
|
5234
|
+
setScopedLayoutMode,
|
|
2818
5235
|
setScopedLayoutOptions,
|
|
2819
5236
|
setScopedLayoutRows,
|
|
5237
|
+
setScopedLayoutVariant,
|
|
2820
5238
|
setTableHints,
|
|
2821
5239
|
setUiEntry,
|
|
5240
|
+
slot,
|
|
2822
5241
|
updateProperty,
|
|
2823
5242
|
useDesignerState,
|
|
2824
5243
|
validateCapabilities
|