radiant-docs 0.1.7 → 0.1.8
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.js +28 -5
- package/package.json +3 -3
- package/template/astro.config.mjs +76 -3
- package/template/package-lock.json +924 -737
- package/template/package.json +7 -5
- package/template/scripts/generate-og-images.mjs +335 -0
- package/template/scripts/generate-og-metadata.mjs +173 -0
- package/template/scripts/rewrite-static-asset-host.mjs +408 -0
- package/template/scripts/stamp-image-versions.mjs +277 -0
- package/template/scripts/stamp-og-image-versions.mjs +199 -0
- package/template/scripts/stamp-pagefind-runtime-version.mjs +140 -0
- package/template/src/assets/fonts/geist-mono/cyrillic.woff2 +0 -0
- package/template/src/assets/fonts/geist-mono/latin-ext.woff2 +0 -0
- package/template/src/assets/fonts/geist-mono/latin.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/canadian-aboriginal.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/cherokee.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/latin-ext.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/latin.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/math.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/nushu.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/symbols.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/syriac.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/tifinagh.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/vietnamese.woff2 +0 -0
- package/template/src/components/Footer.astro +94 -0
- package/template/src/components/Header.astro +11 -66
- package/template/src/components/LogoLink.astro +103 -0
- package/template/src/components/MdxPage.astro +126 -11
- package/template/src/components/OpenApiPage.astro +1036 -69
- package/template/src/components/Search.astro +0 -2
- package/template/src/components/SidebarDropdown.astro +34 -14
- package/template/src/components/SidebarGroup.astro +3 -6
- package/template/src/components/SidebarLink.astro +22 -12
- package/template/src/components/SidebarMenu.astro +19 -16
- package/template/src/components/SidebarSegmented.astro +99 -0
- package/template/src/components/SidebarSubgroup.astro +12 -12
- package/template/src/components/ThemeSwitcher.astro +30 -7
- package/template/src/components/endpoint/PlaygroundBar.astro +32 -36
- package/template/src/components/endpoint/PlaygroundButton.astro +40 -4
- package/template/src/components/endpoint/PlaygroundField.astro +1068 -22
- package/template/src/components/endpoint/PlaygroundForm.astro +559 -61
- package/template/src/components/endpoint/RequestSnippets.astro +342 -193
- package/template/src/components/endpoint/ResponseDisplay.astro +161 -147
- package/template/src/components/endpoint/ResponseFieldTree.astro +134 -0
- package/template/src/components/endpoint/ResponseFields.astro +711 -68
- package/template/src/components/endpoint/ResponseSnippets.astro +299 -173
- package/template/src/components/sidebar/SidebarEndpointLink.astro +1 -1
- package/template/src/components/ui/CodeLanguageIcon.astro +19 -0
- package/template/src/components/ui/CodeTabEdge.astro +79 -0
- package/template/src/components/ui/Field.astro +103 -20
- package/template/src/components/ui/Icon.astro +32 -0
- package/template/src/components/ui/ListChevronsToggle.astro +31 -0
- package/template/src/components/ui/Tag.astro +1 -1
- package/template/src/components/user/{Accordian.astro → Accordion.astro} +6 -6
- package/template/src/components/user/Callout.astro +5 -9
- package/template/src/components/user/CodeBlock.astro +400 -0
- package/template/src/components/user/CodeGroup.astro +225 -0
- package/template/src/components/user/ComponentPreview.astro +1 -0
- package/template/src/components/user/ComponentPreviewBlock.astro +181 -0
- package/template/src/components/user/Image.astro +132 -0
- package/template/src/components/user/Steps.astro +1 -3
- package/template/src/components/user/Tabs.astro +2 -2
- package/template/src/content.config.ts +1 -0
- package/template/src/layouts/Layout.astro +109 -8
- package/template/src/lib/code/code-block.ts +546 -0
- package/template/src/lib/frontmatter-schema.ts +8 -7
- package/template/src/lib/mdx/remark-code-block-component.ts +342 -0
- package/template/src/lib/mdx/remark-demote-h1.ts +16 -0
- package/template/src/lib/pagefind.ts +19 -5
- package/template/src/lib/routes.ts +49 -31
- package/template/src/lib/utils.ts +20 -0
- package/template/src/lib/validation.ts +638 -200
- package/template/src/pages/[...slug].astro +18 -5
- package/template/src/styles/geist-mono.css +33 -0
- package/template/src/styles/global.css +89 -84
- package/template/src/styles/google-sans-flex.css +143 -0
- package/template/ec.config.mjs +0 -51
- /package/template/src/components/user/{AccordianGroup.astro → AccordionGroup.astro} +0 -0
|
@@ -6,13 +6,621 @@ import Field from "../ui/Field.astro";
|
|
|
6
6
|
interface Props {
|
|
7
7
|
field: FieldType;
|
|
8
8
|
requestPart: string;
|
|
9
|
+
scopeExpr?: string;
|
|
10
|
+
defaultsEnabledExpr?: string;
|
|
9
11
|
}
|
|
10
12
|
|
|
11
|
-
const { field, requestPart } = Astro.props;
|
|
13
|
+
const { field, requestPart, scopeExpr, defaultsEnabledExpr } = Astro.props;
|
|
14
|
+
|
|
15
|
+
function cloneDefaultTemplate<T>(value: T): T {
|
|
16
|
+
if (value === undefined) return value;
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(JSON.stringify(value)) as T;
|
|
19
|
+
} catch {
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function buildObjectDefaultTemplateFromFields(
|
|
25
|
+
fields: FieldType[] | undefined,
|
|
26
|
+
): Record<string, unknown> {
|
|
27
|
+
const defaults: Record<string, unknown> = {};
|
|
28
|
+
if (!fields || fields.length === 0) return defaults;
|
|
29
|
+
|
|
30
|
+
fields.forEach((childField) => {
|
|
31
|
+
if (childField.isAdditionalProperty) return;
|
|
32
|
+
const childDefault = buildFieldDefaultTemplate(childField);
|
|
33
|
+
if (childDefault !== undefined) {
|
|
34
|
+
defaults[childField.name] = childDefault;
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return defaults;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function mergeObjectDefaults(
|
|
42
|
+
target: Record<string, unknown>,
|
|
43
|
+
source: Record<string, unknown>,
|
|
44
|
+
) {
|
|
45
|
+
Object.entries(source).forEach(([key, value]) => {
|
|
46
|
+
if (!Object.prototype.hasOwnProperty.call(target, key)) {
|
|
47
|
+
target[key] = value;
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function buildVariantDefaultTemplate(field: FieldType): Record<string, unknown> {
|
|
53
|
+
const variants = field.variants || [];
|
|
54
|
+
if (variants.length === 0) return {};
|
|
55
|
+
|
|
56
|
+
if (field.variantType === "oneOf") {
|
|
57
|
+
for (const variant of variants) {
|
|
58
|
+
const variantDefaults = buildObjectDefaultTemplateFromFields(
|
|
59
|
+
variant.fields,
|
|
60
|
+
);
|
|
61
|
+
if (Object.keys(variantDefaults).length > 0) {
|
|
62
|
+
return variantDefaults;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return {};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const merged: Record<string, unknown> = {};
|
|
69
|
+
variants.forEach((variant) => {
|
|
70
|
+
const variantDefaults = buildObjectDefaultTemplateFromFields(variant.fields);
|
|
71
|
+
mergeObjectDefaults(merged, variantDefaults);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return merged;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function buildFieldDefaultTemplate(field: FieldType): unknown | undefined {
|
|
78
|
+
if (field.hasDefault) {
|
|
79
|
+
return cloneDefaultTemplate(field.defaultValue);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const nestedDefaults = buildObjectDefaultTemplateFromFields(field.nested);
|
|
83
|
+
const variantDefaults = buildVariantDefaultTemplate(field);
|
|
84
|
+
|
|
85
|
+
if (field.isArray) {
|
|
86
|
+
const itemDefaults: Record<string, unknown> = {};
|
|
87
|
+
mergeObjectDefaults(itemDefaults, nestedDefaults);
|
|
88
|
+
mergeObjectDefaults(itemDefaults, variantDefaults);
|
|
89
|
+
|
|
90
|
+
if (field.required && Object.keys(itemDefaults).length > 0) {
|
|
91
|
+
return [itemDefaults];
|
|
92
|
+
}
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const isObjectLikeField =
|
|
97
|
+
(field.nested && field.nested.length > 0) ||
|
|
98
|
+
(field.variants && field.variants.length > 0) ||
|
|
99
|
+
/\bobject\b/.test(field.type);
|
|
100
|
+
if (!isObjectLikeField) return undefined;
|
|
101
|
+
|
|
102
|
+
const objectDefaults: Record<string, unknown> = {};
|
|
103
|
+
mergeObjectDefaults(objectDefaults, nestedDefaults);
|
|
104
|
+
mergeObjectDefaults(objectDefaults, variantDefaults);
|
|
105
|
+
|
|
106
|
+
if (!field.required) return undefined;
|
|
107
|
+
return Object.keys(objectDefaults).length > 0 ? objectDefaults : undefined;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const requestPartKey = JSON.stringify(requestPart);
|
|
111
|
+
const fieldNameKey = JSON.stringify(field.name);
|
|
112
|
+
const defaultScopeExpr = `inputs[${requestPartKey}]`;
|
|
113
|
+
const resolvedScopeExpr = scopeExpr || defaultScopeExpr;
|
|
114
|
+
const resolvedDefaultsEnabledExpr = defaultsEnabledExpr || "true";
|
|
115
|
+
const fieldModelExpr = `${resolvedScopeExpr}[${fieldNameKey}]`;
|
|
116
|
+
const isNumericType = /\b(number|integer)\b/.test(field.type);
|
|
117
|
+
const isIntegerType = /\binteger\b/.test(field.type);
|
|
118
|
+
const hasStringType = /\bstring\b/.test(field.type);
|
|
119
|
+
const isBooleanType = /\bboolean\b/.test(field.type);
|
|
120
|
+
const inputType = isNumericType && !hasStringType ? "number" : "text";
|
|
121
|
+
const isRequiredField = field.required === true;
|
|
122
|
+
const stringMinLength =
|
|
123
|
+
typeof field.minLength === "number" &&
|
|
124
|
+
Number.isInteger(field.minLength) &&
|
|
125
|
+
field.minLength >= 0
|
|
126
|
+
? field.minLength
|
|
127
|
+
: undefined;
|
|
128
|
+
const stringMaxLength =
|
|
129
|
+
typeof field.maxLength === "number" &&
|
|
130
|
+
Number.isInteger(field.maxLength) &&
|
|
131
|
+
field.maxLength >= 0
|
|
132
|
+
? field.maxLength
|
|
133
|
+
: undefined;
|
|
134
|
+
const numericMinimum =
|
|
135
|
+
typeof field.minimum === "number" && Number.isFinite(field.minimum)
|
|
136
|
+
? field.minimum
|
|
137
|
+
: undefined;
|
|
138
|
+
const numericMaximum =
|
|
139
|
+
typeof field.maximum === "number" && Number.isFinite(field.maximum)
|
|
140
|
+
? field.maximum
|
|
141
|
+
: undefined;
|
|
142
|
+
const numericExclusiveMinimum =
|
|
143
|
+
typeof field.exclusiveMinimum === "number" &&
|
|
144
|
+
Number.isFinite(field.exclusiveMinimum)
|
|
145
|
+
? field.exclusiveMinimum
|
|
146
|
+
: undefined;
|
|
147
|
+
const numericExclusiveMaximum =
|
|
148
|
+
typeof field.exclusiveMaximum === "number" &&
|
|
149
|
+
Number.isFinite(field.exclusiveMaximum)
|
|
150
|
+
? field.exclusiveMaximum
|
|
151
|
+
: undefined;
|
|
152
|
+
const numericInputMin =
|
|
153
|
+
inputType === "number"
|
|
154
|
+
? numericMinimum !== undefined
|
|
155
|
+
? numericMinimum
|
|
156
|
+
: isIntegerType && numericExclusiveMinimum !== undefined
|
|
157
|
+
? Math.floor(numericExclusiveMinimum) + 1
|
|
158
|
+
: numericExclusiveMinimum
|
|
159
|
+
: undefined;
|
|
160
|
+
const numericInputMax =
|
|
161
|
+
inputType === "number"
|
|
162
|
+
? numericMaximum !== undefined
|
|
163
|
+
? numericMaximum
|
|
164
|
+
: isIntegerType && numericExclusiveMaximum !== undefined
|
|
165
|
+
? Math.ceil(numericExclusiveMaximum) - 1
|
|
166
|
+
: numericExclusiveMaximum
|
|
167
|
+
: undefined;
|
|
168
|
+
const numericInputStep =
|
|
169
|
+
inputType === "number" ? (isIntegerType ? "1" : "any") : undefined;
|
|
170
|
+
const textInputMinLength = inputType === "text" ? stringMinLength : undefined;
|
|
171
|
+
const textInputMaxLength = inputType === "text" ? stringMaxLength : undefined;
|
|
172
|
+
const hasNumericMinimum = numericMinimum !== undefined;
|
|
173
|
+
const hasNumericMaximum = numericMaximum !== undefined;
|
|
174
|
+
const hasNumericExclusiveMinimum = numericExclusiveMinimum !== undefined;
|
|
175
|
+
const hasNumericExclusiveMaximum = numericExclusiveMaximum !== undefined;
|
|
176
|
+
const hasStringMinLength = stringMinLength !== undefined;
|
|
177
|
+
const hasStringMaxLength = stringMaxLength !== undefined;
|
|
178
|
+
const numericMinimumLiteral =
|
|
179
|
+
numericMinimum !== undefined ? JSON.stringify(numericMinimum) : "undefined";
|
|
180
|
+
const numericMaximumLiteral =
|
|
181
|
+
numericMaximum !== undefined ? JSON.stringify(numericMaximum) : "undefined";
|
|
182
|
+
const numericExclusiveMinimumLiteral =
|
|
183
|
+
numericExclusiveMinimum !== undefined
|
|
184
|
+
? JSON.stringify(numericExclusiveMinimum)
|
|
185
|
+
: "undefined";
|
|
186
|
+
const numericExclusiveMaximumLiteral =
|
|
187
|
+
numericExclusiveMaximum !== undefined
|
|
188
|
+
? JSON.stringify(numericExclusiveMaximum)
|
|
189
|
+
: "undefined";
|
|
190
|
+
const stringMinLengthLiteral =
|
|
191
|
+
stringMinLength !== undefined ? JSON.stringify(stringMinLength) : "undefined";
|
|
192
|
+
const stringMaxLengthLiteral =
|
|
193
|
+
stringMaxLength !== undefined ? JSON.stringify(stringMaxLength) : "undefined";
|
|
194
|
+
const hasNestedFields = Boolean(field.nested && field.nested.length > 0);
|
|
195
|
+
const hasFieldVariants = Boolean(field.variants && field.variants.length > 0);
|
|
196
|
+
const hasOneOfVariants = field.variantType === "oneOf" && hasFieldVariants;
|
|
197
|
+
const oneOfVariantFieldKeys = hasOneOfVariants
|
|
198
|
+
? (field.variants || []).map((variant) =>
|
|
199
|
+
(variant.fields || []).map((variantField) => variantField.name),
|
|
200
|
+
)
|
|
201
|
+
: [];
|
|
202
|
+
const isAdditionalPropertyField = field.isAdditionalProperty === true;
|
|
203
|
+
const mapKnownKeys = JSON.stringify(field.mapKnownKeys || []);
|
|
204
|
+
const oneOfVariantFieldKeysJson = JSON.stringify(oneOfVariantFieldKeys);
|
|
205
|
+
const defaultTemplate = buildFieldDefaultTemplate(field);
|
|
206
|
+
const hasDefaultTemplate = defaultTemplate !== undefined;
|
|
207
|
+
const defaultTemplateJson = JSON.stringify(defaultTemplate ?? null).replaceAll(
|
|
208
|
+
"`",
|
|
209
|
+
"\\`",
|
|
210
|
+
);
|
|
211
|
+
const hasComplexMapValue =
|
|
212
|
+
field.isArray ||
|
|
213
|
+
hasNestedFields ||
|
|
214
|
+
hasFieldVariants ||
|
|
215
|
+
/\bobject\b/.test(field.type);
|
|
216
|
+
const isObjectLikeField =
|
|
217
|
+
isAdditionalPropertyField ||
|
|
218
|
+
hasNestedFields ||
|
|
219
|
+
hasFieldVariants ||
|
|
220
|
+
/\bobject\b/.test(field.type);
|
|
221
|
+
const layoutClass = isObjectLikeField
|
|
222
|
+
? "flex justify-between flex-col gap-4"
|
|
223
|
+
: "flex justify-between flex-col sm:flex-row lg:flex-col xl:flex-row gap-4";
|
|
12
224
|
---
|
|
13
225
|
|
|
14
226
|
<div
|
|
15
|
-
|
|
227
|
+
x-data={`{
|
|
228
|
+
hasDefaultTemplate: ${hasDefaultTemplate ? "true" : "false"},
|
|
229
|
+
defaultTemplate: ${defaultTemplateJson},
|
|
230
|
+
ensureScopeObject() {
|
|
231
|
+
if (
|
|
232
|
+
!${resolvedScopeExpr} ||
|
|
233
|
+
typeof ${resolvedScopeExpr} !== 'object' ||
|
|
234
|
+
Array.isArray(${resolvedScopeExpr})
|
|
235
|
+
) {
|
|
236
|
+
${resolvedScopeExpr} = {};
|
|
237
|
+
}
|
|
238
|
+
return ${resolvedScopeExpr};
|
|
239
|
+
},
|
|
240
|
+
cloneDefaultValue(value) {
|
|
241
|
+
if (value === undefined) return undefined;
|
|
242
|
+
try {
|
|
243
|
+
return JSON.parse(JSON.stringify(value));
|
|
244
|
+
} catch (e) {
|
|
245
|
+
return value;
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
defaultsAreEnabled() {
|
|
249
|
+
try {
|
|
250
|
+
return Boolean(${resolvedDefaultsEnabledExpr});
|
|
251
|
+
} catch (e) {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
applyInitialDefault() {
|
|
256
|
+
if (!this.hasDefaultTemplate || ${isAdditionalPropertyField ? "true" : "false"}) return;
|
|
257
|
+
if (!this.defaultsAreEnabled()) return;
|
|
258
|
+
|
|
259
|
+
this.ensureScopeObject();
|
|
260
|
+
if (Object.prototype.hasOwnProperty.call(${resolvedScopeExpr}, ${fieldNameKey})) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const nextValue = this.cloneDefaultValue(this.defaultTemplate);
|
|
265
|
+
if (nextValue === undefined) return;
|
|
266
|
+
|
|
267
|
+
${fieldModelExpr} = nextValue;
|
|
268
|
+
|
|
269
|
+
if (
|
|
270
|
+
${hasOneOfVariants ? "true" : "false"} &&
|
|
271
|
+
nextValue &&
|
|
272
|
+
typeof nextValue === 'object' &&
|
|
273
|
+
!Array.isArray(nextValue)
|
|
274
|
+
) {
|
|
275
|
+
this.selectedOneOfVariant = this.detectOneOfVariant(nextValue);
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
init() {
|
|
279
|
+
this.applyInitialDefault();
|
|
280
|
+
},
|
|
281
|
+
oneOfVariantFieldKeys: ${oneOfVariantFieldKeysJson},
|
|
282
|
+
selectedOneOfVariant: null,
|
|
283
|
+
selectedArrayItemVariants: {},
|
|
284
|
+
ensureFieldObject() {
|
|
285
|
+
this.ensureScopeObject();
|
|
286
|
+
if (
|
|
287
|
+
!${fieldModelExpr} ||
|
|
288
|
+
typeof ${fieldModelExpr} !== 'object' ||
|
|
289
|
+
Array.isArray(${fieldModelExpr})
|
|
290
|
+
) {
|
|
291
|
+
${fieldModelExpr} = {};
|
|
292
|
+
}
|
|
293
|
+
return ${fieldModelExpr};
|
|
294
|
+
},
|
|
295
|
+
getOneOfVariantFieldKeys(variantIndex) {
|
|
296
|
+
const keys = this.oneOfVariantFieldKeys[variantIndex];
|
|
297
|
+
return Array.isArray(keys) ? keys : [];
|
|
298
|
+
},
|
|
299
|
+
detectOneOfVariant(target) {
|
|
300
|
+
if (!target || typeof target !== 'object' || Array.isArray(target)) {
|
|
301
|
+
return 0;
|
|
302
|
+
}
|
|
303
|
+
let selectedIndex = 0;
|
|
304
|
+
let bestScore = -1;
|
|
305
|
+
this.oneOfVariantFieldKeys.forEach((keys, variantIndex) => {
|
|
306
|
+
if (!Array.isArray(keys) || keys.length === 0) return;
|
|
307
|
+
const score = keys.reduce((total, key) => {
|
|
308
|
+
return total + (Object.prototype.hasOwnProperty.call(target, key) ? 1 : 0);
|
|
309
|
+
}, 0);
|
|
310
|
+
if (score > bestScore) {
|
|
311
|
+
bestScore = score;
|
|
312
|
+
selectedIndex = variantIndex;
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
return selectedIndex;
|
|
316
|
+
},
|
|
317
|
+
pruneObjectForOneOf(target, selectedVariantIndex) {
|
|
318
|
+
if (!target || typeof target !== 'object' || Array.isArray(target)) return;
|
|
319
|
+
const selectedKeys = new Set(
|
|
320
|
+
this.getOneOfVariantFieldKeys(selectedVariantIndex),
|
|
321
|
+
);
|
|
322
|
+
const keysToRemove = new Set();
|
|
323
|
+
this.oneOfVariantFieldKeys.forEach((keys, variantIndex) => {
|
|
324
|
+
if (!Array.isArray(keys) || variantIndex === selectedVariantIndex) return;
|
|
325
|
+
keys.forEach((key) => {
|
|
326
|
+
if (!selectedKeys.has(key)) keysToRemove.add(key);
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
keysToRemove.forEach((key) => {
|
|
330
|
+
delete target[key];
|
|
331
|
+
});
|
|
332
|
+
},
|
|
333
|
+
getSelectedOneOfVariant() {
|
|
334
|
+
if (Number.isInteger(this.selectedOneOfVariant)) {
|
|
335
|
+
return this.selectedOneOfVariant;
|
|
336
|
+
}
|
|
337
|
+
const target = ${fieldModelExpr};
|
|
338
|
+
const detected = this.detectOneOfVariant(target);
|
|
339
|
+
this.selectedOneOfVariant = detected;
|
|
340
|
+
return detected;
|
|
341
|
+
},
|
|
342
|
+
selectOneOfVariant(variantIndex) {
|
|
343
|
+
this.selectedOneOfVariant = variantIndex;
|
|
344
|
+
const target = this.ensureFieldObject();
|
|
345
|
+
this.pruneObjectForOneOf(target, variantIndex);
|
|
346
|
+
},
|
|
347
|
+
getSelectedArrayItemVariant(itemIndex) {
|
|
348
|
+
if (Object.prototype.hasOwnProperty.call(this.selectedArrayItemVariants, itemIndex)) {
|
|
349
|
+
return this.selectedArrayItemVariants[itemIndex];
|
|
350
|
+
}
|
|
351
|
+
const values = this.getArrayObjectValues();
|
|
352
|
+
const detected = this.detectOneOfVariant(values[itemIndex]);
|
|
353
|
+
this.selectedArrayItemVariants[itemIndex] = detected;
|
|
354
|
+
return detected;
|
|
355
|
+
},
|
|
356
|
+
selectArrayItemVariant(itemIndex, variantIndex) {
|
|
357
|
+
this.selectedArrayItemVariants[itemIndex] = variantIndex;
|
|
358
|
+
const target = this.ensureArrayObjectItem(itemIndex);
|
|
359
|
+
this.pruneObjectForOneOf(target, variantIndex);
|
|
360
|
+
},
|
|
361
|
+
shiftArrayItemVariantSelection(removedIndex) {
|
|
362
|
+
const nextSelections = {};
|
|
363
|
+
Object.entries(this.selectedArrayItemVariants).forEach(([rawIndex, variantIndex]) => {
|
|
364
|
+
const index = Number(rawIndex);
|
|
365
|
+
if (!Number.isInteger(index) || index === removedIndex) return;
|
|
366
|
+
nextSelections[index > removedIndex ? index - 1 : index] = variantIndex;
|
|
367
|
+
});
|
|
368
|
+
this.selectedArrayItemVariants = nextSelections;
|
|
369
|
+
},
|
|
370
|
+
mapKnownKeys: ${mapKnownKeys},
|
|
371
|
+
getMapEntryKeys() {
|
|
372
|
+
const target = this.ensureScopeObject();
|
|
373
|
+
return Object.keys(target).filter((key) => !this.mapKnownKeys.includes(key));
|
|
374
|
+
},
|
|
375
|
+
addMapEntry() {
|
|
376
|
+
const target = this.ensureScopeObject();
|
|
377
|
+
const baseKey = 'key';
|
|
378
|
+
let nextKey = baseKey;
|
|
379
|
+
let index = 1;
|
|
380
|
+
while (Object.prototype.hasOwnProperty.call(target, nextKey)) {
|
|
381
|
+
nextKey = baseKey + '_' + index;
|
|
382
|
+
index += 1;
|
|
383
|
+
}
|
|
384
|
+
target[nextKey] = '';
|
|
385
|
+
},
|
|
386
|
+
removeMapEntry(mapKey) {
|
|
387
|
+
const target = this.ensureScopeObject();
|
|
388
|
+
delete target[mapKey];
|
|
389
|
+
},
|
|
390
|
+
renameMapKey(oldKey, newKey) {
|
|
391
|
+
const target = this.ensureScopeObject();
|
|
392
|
+
const nextKey = String(newKey || '').trim();
|
|
393
|
+
if (!nextKey || nextKey === oldKey) return;
|
|
394
|
+
if (Object.prototype.hasOwnProperty.call(target, nextKey)) return;
|
|
395
|
+
target[nextKey] = target[oldKey];
|
|
396
|
+
delete target[oldKey];
|
|
397
|
+
},
|
|
398
|
+
getMapValue(mapKey) {
|
|
399
|
+
const target = this.ensureScopeObject();
|
|
400
|
+
const value = target[mapKey];
|
|
401
|
+
return value === undefined || value === null ? '' : value;
|
|
402
|
+
},
|
|
403
|
+
setMapValue(mapKey, value, element) {
|
|
404
|
+
const target = this.ensureScopeObject();
|
|
405
|
+
target[mapKey] = this.coerceScalarValue(value);
|
|
406
|
+
this.setScalarValidity(element, value);
|
|
407
|
+
},
|
|
408
|
+
coerceScalarValue(rawValue) {
|
|
409
|
+
const value = rawValue === undefined || rawValue === null ? '' : String(rawValue).trim();
|
|
410
|
+
if (value === '__clear__') return '';
|
|
411
|
+
if (value === '') return '';
|
|
412
|
+
if (!${isNumericType}) return value;
|
|
413
|
+
|
|
414
|
+
const numeric = Number(value);
|
|
415
|
+
if (!Number.isFinite(numeric)) return value;
|
|
416
|
+
|
|
417
|
+
if (${isIntegerType} && !Number.isInteger(numeric)) {
|
|
418
|
+
return value;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return numeric;
|
|
422
|
+
},
|
|
423
|
+
setScalarValidity(element, rawValue) {
|
|
424
|
+
if (!element || typeof element.setCustomValidity !== 'function') return;
|
|
425
|
+
const value = rawValue === undefined || rawValue === null ? '' : String(rawValue).trim();
|
|
426
|
+
if (value === '' || value === '__clear__') {
|
|
427
|
+
element.setCustomValidity('');
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (${isNumericType}) {
|
|
432
|
+
const numeric = Number(value);
|
|
433
|
+
if (!Number.isFinite(numeric)) {
|
|
434
|
+
element.setCustomValidity('Enter a valid number.');
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
if (${isIntegerType} && !Number.isInteger(numeric)) {
|
|
438
|
+
element.setCustomValidity('Enter a whole number.');
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
if (${hasNumericMinimum ? "true" : "false"} && numeric < ${numericMinimumLiteral}) {
|
|
442
|
+
element.setCustomValidity('Must be greater than or equal to ${numericMinimum ?? ""}.');
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
if (${hasNumericExclusiveMinimum ? "true" : "false"} && numeric <= ${numericExclusiveMinimumLiteral}) {
|
|
446
|
+
element.setCustomValidity('Must be greater than ${numericExclusiveMinimum ?? ""}.');
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
if (${hasNumericMaximum ? "true" : "false"} && numeric > ${numericMaximumLiteral}) {
|
|
450
|
+
element.setCustomValidity('Must be less than or equal to ${numericMaximum ?? ""}.');
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
if (${hasNumericExclusiveMaximum ? "true" : "false"} && numeric >= ${numericExclusiveMaximumLiteral}) {
|
|
454
|
+
element.setCustomValidity('Must be less than ${numericExclusiveMaximum ?? ""}.');
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
} else if (${hasStringType || hasStringMinLength || hasStringMaxLength ? "true" : "false"}) {
|
|
458
|
+
if (${hasStringMinLength ? "true" : "false"} && value.length < ${stringMinLengthLiteral}) {
|
|
459
|
+
element.setCustomValidity('Must be at least ${stringMinLength ?? ""} characters.');
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
if (${hasStringMaxLength ? "true" : "false"} && value.length > ${stringMaxLengthLiteral}) {
|
|
463
|
+
element.setCustomValidity('Must be at most ${stringMaxLength ?? ""} characters.');
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
element.setCustomValidity('');
|
|
469
|
+
},
|
|
470
|
+
reportFieldValidity(element) {
|
|
471
|
+
if (!element || typeof element.reportValidity !== 'function') return true;
|
|
472
|
+
this.setScalarValidity(element, element.value);
|
|
473
|
+
return element.reportValidity();
|
|
474
|
+
},
|
|
475
|
+
toBooleanOption(value) {
|
|
476
|
+
if (value === true || value === 'true') return 'true';
|
|
477
|
+
if (value === false || value === 'false') return 'false';
|
|
478
|
+
return '';
|
|
479
|
+
},
|
|
480
|
+
getBooleanFieldValue() {
|
|
481
|
+
return this.toBooleanOption(${fieldModelExpr});
|
|
482
|
+
},
|
|
483
|
+
setBooleanFieldValue(rawValue) {
|
|
484
|
+
const value = String(rawValue || '');
|
|
485
|
+
this.ensureScopeObject();
|
|
486
|
+
if (value === 'true') {
|
|
487
|
+
${fieldModelExpr} = true;
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
if (value === 'false') {
|
|
491
|
+
${fieldModelExpr} = false;
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
delete ${resolvedScopeExpr}[${fieldNameKey}];
|
|
495
|
+
},
|
|
496
|
+
getArrayBooleanValue(index) {
|
|
497
|
+
const values = this.getArrayValues();
|
|
498
|
+
return this.toBooleanOption(values[index]);
|
|
499
|
+
},
|
|
500
|
+
setArrayBooleanValue(index, rawValue) {
|
|
501
|
+
const value = String(rawValue || '');
|
|
502
|
+
const values = this.ensureArrayField();
|
|
503
|
+
if (value === 'true') {
|
|
504
|
+
values[index] = true;
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
if (value === 'false') {
|
|
508
|
+
values[index] = false;
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
values[index] = '';
|
|
512
|
+
},
|
|
513
|
+
getMapBooleanValue(mapKey) {
|
|
514
|
+
return this.toBooleanOption(this.getMapValue(mapKey));
|
|
515
|
+
},
|
|
516
|
+
setMapBooleanValue(mapKey, rawValue) {
|
|
517
|
+
const value = String(rawValue || '');
|
|
518
|
+
const target = this.ensureScopeObject();
|
|
519
|
+
if (value === 'true') {
|
|
520
|
+
target[mapKey] = true;
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
if (value === 'false') {
|
|
524
|
+
target[mapKey] = false;
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
target[mapKey] = '';
|
|
528
|
+
},
|
|
529
|
+
getMapJsonValue(mapKey) {
|
|
530
|
+
const target = this.ensureScopeObject();
|
|
531
|
+
const value = target[mapKey];
|
|
532
|
+
if (value === undefined) return '';
|
|
533
|
+
try {
|
|
534
|
+
return JSON.stringify(value, null, 2);
|
|
535
|
+
} catch (e) {
|
|
536
|
+
return '';
|
|
537
|
+
}
|
|
538
|
+
},
|
|
539
|
+
setMapJsonValue(mapKey, jsonValue) {
|
|
540
|
+
const target = this.ensureScopeObject();
|
|
541
|
+
const raw = String(jsonValue || '');
|
|
542
|
+
if (!raw.trim()) {
|
|
543
|
+
target[mapKey] = '';
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
try {
|
|
547
|
+
target[mapKey] = JSON.parse(raw);
|
|
548
|
+
} catch (e) {
|
|
549
|
+
// Keep previous valid value while editing invalid JSON.
|
|
550
|
+
}
|
|
551
|
+
},
|
|
552
|
+
getFieldValue() {
|
|
553
|
+
const value = ${fieldModelExpr};
|
|
554
|
+
return value === undefined || value === null ? '' : value;
|
|
555
|
+
},
|
|
556
|
+
setFieldValue(value, element) {
|
|
557
|
+
this.ensureScopeObject();
|
|
558
|
+
${fieldModelExpr} = this.coerceScalarValue(value);
|
|
559
|
+
this.setScalarValidity(element, value);
|
|
560
|
+
},
|
|
561
|
+
ensureArrayField() {
|
|
562
|
+
this.ensureScopeObject();
|
|
563
|
+
if (!Array.isArray(${fieldModelExpr})) {
|
|
564
|
+
${fieldModelExpr} = [];
|
|
565
|
+
}
|
|
566
|
+
return ${fieldModelExpr};
|
|
567
|
+
},
|
|
568
|
+
getArrayValues() {
|
|
569
|
+
const values = ${fieldModelExpr};
|
|
570
|
+
return Array.isArray(values) && values.length > 0 ? values : [''];
|
|
571
|
+
},
|
|
572
|
+
setArrayValue(index, value, element) {
|
|
573
|
+
const values = this.ensureArrayField();
|
|
574
|
+
values[index] = this.coerceScalarValue(value);
|
|
575
|
+
this.setScalarValidity(element, value);
|
|
576
|
+
},
|
|
577
|
+
addArrayValue() {
|
|
578
|
+
const values = this.ensureArrayField();
|
|
579
|
+
if (values.length === 0) {
|
|
580
|
+
values.push('', '');
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
values.push('');
|
|
584
|
+
},
|
|
585
|
+
removeArrayValue(index) {
|
|
586
|
+
const values = ${fieldModelExpr};
|
|
587
|
+
if (!Array.isArray(values)) return;
|
|
588
|
+
values.splice(index, 1);
|
|
589
|
+
if (values.length === 0) {
|
|
590
|
+
delete ${resolvedScopeExpr}[${fieldNameKey}];
|
|
591
|
+
}
|
|
592
|
+
},
|
|
593
|
+
getArrayObjectValues() {
|
|
594
|
+
const values = ${fieldModelExpr};
|
|
595
|
+
return Array.isArray(values) ? values : [];
|
|
596
|
+
},
|
|
597
|
+
ensureArrayObjectItem(index) {
|
|
598
|
+
const values = this.ensureArrayField();
|
|
599
|
+
if (
|
|
600
|
+
!values[index] ||
|
|
601
|
+
typeof values[index] !== 'object' ||
|
|
602
|
+
Array.isArray(values[index])
|
|
603
|
+
) {
|
|
604
|
+
values[index] = {};
|
|
605
|
+
}
|
|
606
|
+
return values[index];
|
|
607
|
+
},
|
|
608
|
+
addArrayObjectValue() {
|
|
609
|
+
const values = this.ensureArrayField();
|
|
610
|
+
values.push({});
|
|
611
|
+
},
|
|
612
|
+
removeArrayObjectValue(index) {
|
|
613
|
+
const values = ${fieldModelExpr};
|
|
614
|
+
if (!Array.isArray(values)) return;
|
|
615
|
+
values.splice(index, 1);
|
|
616
|
+
this.shiftArrayItemVariantSelection(index);
|
|
617
|
+
if (values.length === 0) {
|
|
618
|
+
delete ${resolvedScopeExpr}[${fieldNameKey}];
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}`}
|
|
622
|
+
x-effect="applyInitialDefault()"
|
|
623
|
+
class={layoutClass}
|
|
16
624
|
>
|
|
17
625
|
<div class="w-full mt-1.5">
|
|
18
626
|
<Field
|
|
@@ -20,34 +628,472 @@ const { field, requestPart } = Astro.props;
|
|
|
20
628
|
type={field.type}
|
|
21
629
|
description={field.description}
|
|
22
630
|
required={field.required}
|
|
631
|
+
minLength={field.minLength}
|
|
632
|
+
maxLength={field.maxLength}
|
|
633
|
+
minimum={field.minimum}
|
|
634
|
+
maximum={field.maximum}
|
|
635
|
+
exclusiveMinimum={field.exclusiveMinimum}
|
|
636
|
+
exclusiveMaximum={field.exclusiveMaximum}
|
|
23
637
|
/>
|
|
24
638
|
</div>
|
|
25
639
|
<div class="w-full">
|
|
26
640
|
{
|
|
27
|
-
|
|
28
|
-
<div class="
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class="w-full px-3 py-1.5 text-sm border border-neutral-200 focus:border-neutral-300 outline-none rounded-md shadow-xs focus:shadow placeholder:text-neutral-400 transition-all duration-200 bg-white appearance-none"
|
|
641
|
+
isAdditionalPropertyField ? (
|
|
642
|
+
<div class="space-y-2">
|
|
643
|
+
<template
|
|
644
|
+
x-for={`(mapKey, mapIndex) in getMapEntryKeys()`}
|
|
645
|
+
:key={`${fieldNameKey} + '-map-' + mapKey + '-' + mapIndex`}
|
|
33
646
|
>
|
|
34
|
-
|
|
35
|
-
<
|
|
647
|
+
<div class="rounded-lg border border-neutral-200 bg-neutral-50/50 p-2.5">
|
|
648
|
+
<div class="mb-2 flex items-center gap-2">
|
|
649
|
+
<input
|
|
650
|
+
type="text"
|
|
651
|
+
:value="mapKey"
|
|
652
|
+
@change="renameMapKey(mapKey, $event.target.value)"
|
|
653
|
+
class="w-full px-3 py-1.5 text-sm border border-neutral-200 focus:border-neutral-300 outline-none rounded-md shadow-xs focus:shadow placeholder:text-neutral-400 transition-all duration-200 bg-white"
|
|
654
|
+
placeholder="Property key"
|
|
655
|
+
/>
|
|
656
|
+
<button
|
|
657
|
+
type="button"
|
|
658
|
+
@click="removeMapEntry(mapKey)"
|
|
659
|
+
class="h-[31px] px-2 rounded-md border border-neutral-200 text-neutral-500 hover:text-neutral-700 hover:border-neutral-300 hover:bg-white transition-colors duration-200 cursor-pointer"
|
|
660
|
+
aria-label={`Remove ${field.name} key`}
|
|
661
|
+
>
|
|
662
|
+
<Icon class="size-4" name="lucide:x" />
|
|
663
|
+
</button>
|
|
664
|
+
</div>
|
|
665
|
+
{
|
|
666
|
+
hasComplexMapValue ? (
|
|
667
|
+
<textarea
|
|
668
|
+
rows="3"
|
|
669
|
+
:value="getMapJsonValue(mapKey)"
|
|
670
|
+
@input="setMapJsonValue(mapKey, $event.target.value)"
|
|
671
|
+
class="w-full px-3 py-2 text-sm border border-neutral-200 focus:border-neutral-300 outline-none rounded-md shadow-xs focus:shadow placeholder:text-neutral-400 transition-all duration-200 bg-white font-mono"
|
|
672
|
+
placeholder="JSON value"
|
|
673
|
+
/>
|
|
674
|
+
) : field.enum && field.enum.length > 0 ? (
|
|
675
|
+
<div class="relative">
|
|
676
|
+
<select
|
|
677
|
+
:value="getMapValue(mapKey)"
|
|
678
|
+
@change="setMapValue(mapKey, $event.target.value, $event.target)"
|
|
679
|
+
:class="getMapValue(mapKey) ? 'text-neutral-700' : 'text-neutral-400'"
|
|
680
|
+
class="w-full px-3 py-1.5 text-sm border border-neutral-200 focus:border-neutral-300 outline-none rounded-md shadow-xs focus:shadow transition-all duration-200 bg-white appearance-none"
|
|
681
|
+
>
|
|
682
|
+
<option value="" disabled hidden>Select value</option>
|
|
683
|
+
<option value="__clear__">Clear selection</option>
|
|
684
|
+
{field.enum.map((value) => (
|
|
685
|
+
<option value={String(value)}>{value}</option>
|
|
686
|
+
))}
|
|
687
|
+
</select>
|
|
688
|
+
<Icon
|
|
689
|
+
class="absolute right-3 top-1/2 -translate-y-1/2 size-4 pointer-events-none text-neutral-400"
|
|
690
|
+
name="lucide:chevrons-up-down"
|
|
691
|
+
/>
|
|
692
|
+
</div>
|
|
693
|
+
) : isBooleanType ? (
|
|
694
|
+
<div class="relative">
|
|
695
|
+
<select
|
|
696
|
+
:value="getMapBooleanValue(mapKey)"
|
|
697
|
+
@change="setMapBooleanValue(mapKey, $event.target.value)"
|
|
698
|
+
:class="getMapBooleanValue(mapKey) ? 'text-neutral-700' : 'text-neutral-400'"
|
|
699
|
+
class="w-full px-3 py-1.5 text-sm border border-neutral-200 focus:border-neutral-300 outline-none rounded-md shadow-xs focus:shadow transition-all duration-200 bg-white appearance-none"
|
|
700
|
+
>
|
|
701
|
+
<option value="" disabled hidden>Select value</option>
|
|
702
|
+
<option value="__clear__">Clear selection</option>
|
|
703
|
+
<option value="true">true</option>
|
|
704
|
+
<option value="false">false</option>
|
|
705
|
+
</select>
|
|
706
|
+
<Icon
|
|
707
|
+
class="absolute right-3 top-1/2 -translate-y-1/2 size-4 pointer-events-none text-neutral-400"
|
|
708
|
+
name="lucide:chevrons-up-down"
|
|
709
|
+
/>
|
|
710
|
+
</div>
|
|
711
|
+
) : (
|
|
712
|
+
<input
|
|
713
|
+
type={inputType}
|
|
714
|
+
:value="getMapValue(mapKey)"
|
|
715
|
+
@input="setMapValue(mapKey, $event.target.value, $event.target)"
|
|
716
|
+
@blur="reportFieldValidity($event.target)"
|
|
717
|
+
min={numericInputMin}
|
|
718
|
+
max={numericInputMax}
|
|
719
|
+
step={numericInputStep}
|
|
720
|
+
minlength={textInputMinLength}
|
|
721
|
+
maxlength={textInputMaxLength}
|
|
722
|
+
class="w-full px-3 py-1.5 text-sm border border-neutral-200 focus:border-neutral-300 outline-none rounded-md shadow-xs focus:shadow placeholder:text-neutral-400 transition-all duration-200 bg-white"
|
|
723
|
+
placeholder="Property value"
|
|
724
|
+
/>
|
|
725
|
+
)
|
|
726
|
+
}
|
|
727
|
+
</div>
|
|
728
|
+
</template>
|
|
729
|
+
<button
|
|
730
|
+
type="button"
|
|
731
|
+
@click="addMapEntry()"
|
|
732
|
+
class="inline-flex items-center gap-1 text-xs font-medium text-neutral-700 hover:text-neutral-900 cursor-pointer transition-colors duration-200"
|
|
733
|
+
>
|
|
734
|
+
<Icon class="size-3.5" name="lucide:plus" />
|
|
735
|
+
Add property
|
|
736
|
+
</button>
|
|
737
|
+
</div>
|
|
738
|
+
) : field.isArray ? (
|
|
739
|
+
hasNestedFields || hasFieldVariants ? (
|
|
740
|
+
<div class="space-y-2">
|
|
741
|
+
<template
|
|
742
|
+
x-for={`(item, index) in getArrayObjectValues()`}
|
|
743
|
+
:key={`${fieldNameKey} + '-item-' + index`}
|
|
744
|
+
>
|
|
745
|
+
<div
|
|
746
|
+
class="rounded-lg border border-neutral-200 bg-neutral-50/50 p-2.5"
|
|
747
|
+
x-init="ensureArrayObjectItem(index)"
|
|
748
|
+
>
|
|
749
|
+
<div class="mb-2 flex items-center justify-between">
|
|
750
|
+
<span class="text-xs font-medium text-neutral-600">
|
|
751
|
+
Item <span x-text="index + 1" />
|
|
752
|
+
</span>
|
|
753
|
+
<button
|
|
754
|
+
type="button"
|
|
755
|
+
@click="removeArrayObjectValue(index)"
|
|
756
|
+
class="inline-flex h-6 items-center rounded-md border border-neutral-200 px-2 text-[11px] font-medium text-neutral-600 hover:border-neutral-300 hover:bg-white hover:text-neutral-700 transition-colors duration-200 cursor-pointer"
|
|
757
|
+
aria-label={`Remove ${field.name} item`}
|
|
758
|
+
>
|
|
759
|
+
Remove
|
|
760
|
+
</button>
|
|
761
|
+
</div>
|
|
762
|
+
<div class="space-y-3">
|
|
763
|
+
{field.nested?.map((nestedField) => (
|
|
764
|
+
<div class="border-b border-b-neutral-100 last:border-none pb-3 last:pb-0">
|
|
765
|
+
<Astro.self
|
|
766
|
+
field={nestedField}
|
|
767
|
+
requestPart={requestPart}
|
|
768
|
+
scopeExpr={`${fieldModelExpr}[index]`}
|
|
769
|
+
defaultsEnabledExpr={resolvedDefaultsEnabledExpr}
|
|
770
|
+
/>
|
|
771
|
+
</div>
|
|
772
|
+
))}
|
|
773
|
+
{field.variants && field.variants.length > 0 && (
|
|
774
|
+
<div class:list={["space-y-2", hasNestedFields && "pt-1"]}>
|
|
775
|
+
{hasOneOfVariants ? (
|
|
776
|
+
<>
|
|
777
|
+
<p class="text-xs text-neutral-500">
|
|
778
|
+
Select one variant.
|
|
779
|
+
</p>
|
|
780
|
+
<div class="inline-flex max-w-full flex-wrap items-center gap-1 rounded-lg bg-neutral-100 p-1">
|
|
781
|
+
{field.variants.map((variant, variantIndex) => (
|
|
782
|
+
<button
|
|
783
|
+
type="button"
|
|
784
|
+
@click={`selectArrayItemVariant(index, ${variantIndex})`}
|
|
785
|
+
:class={`{
|
|
786
|
+
'bg-white text-neutral-900 shadow-xs ring-1 ring-neutral-200': getSelectedArrayItemVariant(index) === ${variantIndex},
|
|
787
|
+
'text-neutral-600 hover:bg-neutral-50 hover:text-neutral-800': getSelectedArrayItemVariant(index) !== ${variantIndex}
|
|
788
|
+
}`}
|
|
789
|
+
class="inline-flex h-7 items-center justify-center whitespace-nowrap rounded-md px-2.5 text-[11px] font-medium transition-all duration-200 cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-300"
|
|
790
|
+
>
|
|
791
|
+
{variant.label}
|
|
792
|
+
</button>
|
|
793
|
+
))}
|
|
794
|
+
</div>
|
|
795
|
+
{field.variants.map((variant, variantIndex) => (
|
|
796
|
+
<div
|
|
797
|
+
x-show={`getSelectedArrayItemVariant(index) === ${variantIndex}`}
|
|
798
|
+
x-cloak
|
|
799
|
+
>
|
|
800
|
+
<div class="rounded-md border border-neutral-200 bg-white p-2.5">
|
|
801
|
+
<div class="mb-2 text-xs font-medium text-neutral-600">
|
|
802
|
+
{variant.label}
|
|
803
|
+
</div>
|
|
804
|
+
<div class="space-y-3">
|
|
805
|
+
{variant.fields.map((variantField) => (
|
|
806
|
+
<div class="border-b border-b-neutral-100 last:border-none pb-3 last:pb-0">
|
|
807
|
+
<Astro.self
|
|
808
|
+
field={variantField}
|
|
809
|
+
requestPart={requestPart}
|
|
810
|
+
scopeExpr={`${fieldModelExpr}[index]`}
|
|
811
|
+
defaultsEnabledExpr={`(${resolvedDefaultsEnabledExpr}) && getSelectedArrayItemVariant(index) === ${variantIndex}`}
|
|
812
|
+
/>
|
|
813
|
+
</div>
|
|
814
|
+
))}
|
|
815
|
+
</div>
|
|
816
|
+
</div>
|
|
817
|
+
</div>
|
|
818
|
+
))}
|
|
819
|
+
</>
|
|
820
|
+
) : (
|
|
821
|
+
<>
|
|
822
|
+
<p class="text-xs text-neutral-500">
|
|
823
|
+
One or more variants may apply.
|
|
824
|
+
</p>
|
|
825
|
+
{field.variants.map((variant) => (
|
|
826
|
+
<div class="rounded-md border border-neutral-200 bg-white p-2.5">
|
|
827
|
+
<div class="mb-2 text-xs font-medium text-neutral-600">
|
|
828
|
+
{variant.label}
|
|
829
|
+
</div>
|
|
830
|
+
<div class="space-y-3">
|
|
831
|
+
{variant.fields.map((variantField) => (
|
|
832
|
+
<div class="border-b border-b-neutral-100 last:border-none pb-3 last:pb-0">
|
|
833
|
+
<Astro.self
|
|
834
|
+
field={variantField}
|
|
835
|
+
requestPart={requestPart}
|
|
836
|
+
scopeExpr={`${fieldModelExpr}[index]`}
|
|
837
|
+
defaultsEnabledExpr={resolvedDefaultsEnabledExpr}
|
|
838
|
+
/>
|
|
839
|
+
</div>
|
|
840
|
+
))}
|
|
841
|
+
</div>
|
|
842
|
+
</div>
|
|
843
|
+
))}
|
|
844
|
+
</>
|
|
845
|
+
)}
|
|
846
|
+
</div>
|
|
847
|
+
)}
|
|
848
|
+
</div>
|
|
849
|
+
</div>
|
|
850
|
+
</template>
|
|
851
|
+
<button
|
|
852
|
+
type="button"
|
|
853
|
+
@click="addArrayObjectValue()"
|
|
854
|
+
class="inline-flex items-center gap-1 text-xs font-medium text-neutral-700 hover:text-neutral-900 cursor-pointer transition-colors duration-200"
|
|
855
|
+
>
|
|
856
|
+
<Icon class="size-3.5" name="lucide:plus" />
|
|
857
|
+
Add item
|
|
858
|
+
</button>
|
|
859
|
+
</div>
|
|
860
|
+
) : (
|
|
861
|
+
<div class="space-y-2">
|
|
862
|
+
<template x-for={`(value, index) in getArrayValues()`} :key={`${fieldNameKey} + '-' + index`}>
|
|
863
|
+
<div class="flex items-center gap-2">
|
|
864
|
+
{
|
|
865
|
+
field.enum && field.enum.length > 0 ? (
|
|
866
|
+
<div class="relative w-full">
|
|
867
|
+
<select
|
|
868
|
+
name={`${requestPart}_${field.name}`}
|
|
869
|
+
:value="value"
|
|
870
|
+
@change={`setArrayValue(index, $event.target.value, $event.target)`}
|
|
871
|
+
:required={`index === 0 && ${isRequiredField ? "true" : "false"}`}
|
|
872
|
+
:class="value ? 'text-neutral-700' : 'text-neutral-400'"
|
|
873
|
+
class="w-full px-3 py-1.5 text-sm border border-neutral-200 focus:border-neutral-300 outline-none rounded-md shadow-xs focus:shadow transition-all duration-200 bg-white appearance-none"
|
|
874
|
+
>
|
|
875
|
+
<option value="" disabled hidden>Select value</option>
|
|
876
|
+
<option value="__clear__">Clear selection</option>
|
|
877
|
+
{field.enum.map((value) => (
|
|
878
|
+
<option value={String(value)}>{value}</option>
|
|
879
|
+
))}
|
|
880
|
+
</select>
|
|
881
|
+
<Icon
|
|
882
|
+
class="absolute right-3 top-1/2 -translate-y-1/2 size-4 pointer-events-none text-neutral-400"
|
|
883
|
+
name="lucide:chevrons-up-down"
|
|
884
|
+
/>
|
|
885
|
+
</div>
|
|
886
|
+
) : (
|
|
887
|
+
isBooleanType ? (
|
|
888
|
+
<div class="relative w-full">
|
|
889
|
+
<select
|
|
890
|
+
name={`${requestPart}_${field.name}`}
|
|
891
|
+
:value="getArrayBooleanValue(index)"
|
|
892
|
+
@change={`setArrayBooleanValue(index, $event.target.value)`}
|
|
893
|
+
:required={`index === 0 && ${isRequiredField ? "true" : "false"}`}
|
|
894
|
+
:class="getArrayBooleanValue(index) ? 'text-neutral-700' : 'text-neutral-400'"
|
|
895
|
+
class="w-full px-3 py-1.5 text-sm border border-neutral-200 focus:border-neutral-300 outline-none rounded-md shadow-xs focus:shadow transition-all duration-200 bg-white appearance-none"
|
|
896
|
+
>
|
|
897
|
+
<option value="" disabled hidden>Select value</option>
|
|
898
|
+
<option value="__clear__">Clear selection</option>
|
|
899
|
+
<option value="true">true</option>
|
|
900
|
+
<option value="false">false</option>
|
|
901
|
+
</select>
|
|
902
|
+
<Icon
|
|
903
|
+
class="absolute right-3 top-1/2 -translate-y-1/2 size-4 pointer-events-none text-neutral-400"
|
|
904
|
+
name="lucide:chevrons-up-down"
|
|
905
|
+
/>
|
|
906
|
+
</div>
|
|
907
|
+
) : (
|
|
908
|
+
<input
|
|
909
|
+
name={field.name}
|
|
910
|
+
placeholder={`Enter ${field.name}`}
|
|
911
|
+
type={inputType}
|
|
912
|
+
:value="value"
|
|
913
|
+
@input={`setArrayValue(index, $event.target.value, $event.target)`}
|
|
914
|
+
@blur="reportFieldValidity($event.target)"
|
|
915
|
+
:required={`index === 0 && ${isRequiredField ? "true" : "false"}`}
|
|
916
|
+
min={numericInputMin}
|
|
917
|
+
max={numericInputMax}
|
|
918
|
+
step={numericInputStep}
|
|
919
|
+
minlength={textInputMinLength}
|
|
920
|
+
maxlength={textInputMaxLength}
|
|
921
|
+
class="w-full px-3 py-1.5 text-sm border border-neutral-200 focus:border-neutral-300 outline-none rounded-md shadow-xs focus:shadow placeholder:text-neutral-400 transition-all duration-200"
|
|
922
|
+
/>
|
|
923
|
+
)
|
|
924
|
+
)
|
|
925
|
+
}
|
|
926
|
+
<button
|
|
927
|
+
type="button"
|
|
928
|
+
@click="removeArrayValue(index)"
|
|
929
|
+
class="h-[31px] px-2 rounded-md border border-neutral-200 text-neutral-500 hover:text-neutral-700 hover:border-neutral-300 hover:bg-neutral-50 transition-colors duration-200 cursor-pointer"
|
|
930
|
+
aria-label={`Remove ${field.name} value`}
|
|
931
|
+
>
|
|
932
|
+
<Icon class="size-4" name="lucide:x" />
|
|
933
|
+
</button>
|
|
934
|
+
</div>
|
|
935
|
+
</template>
|
|
936
|
+
<button
|
|
937
|
+
type="button"
|
|
938
|
+
@click="addArrayValue()"
|
|
939
|
+
class="inline-flex items-center gap-1 text-xs font-medium text-neutral-700 hover:text-neutral-900 cursor-pointer transition-colors duration-200"
|
|
940
|
+
>
|
|
941
|
+
<Icon class="size-3.5" name="lucide:plus" />
|
|
942
|
+
Add value
|
|
943
|
+
</button>
|
|
944
|
+
</div>
|
|
945
|
+
)
|
|
946
|
+
) : hasNestedFields || hasFieldVariants ? (
|
|
947
|
+
<div class="rounded-lg border border-neutral-200 bg-neutral-50/50 p-2.5">
|
|
948
|
+
<div class="space-y-3">
|
|
949
|
+
{field.nested?.map((nestedField) => (
|
|
950
|
+
<div class="border-b border-b-neutral-100 last:border-none pb-3 last:pb-0">
|
|
951
|
+
<Astro.self
|
|
952
|
+
field={nestedField}
|
|
953
|
+
requestPart={requestPart}
|
|
954
|
+
scopeExpr={fieldModelExpr}
|
|
955
|
+
defaultsEnabledExpr={resolvedDefaultsEnabledExpr}
|
|
956
|
+
/>
|
|
957
|
+
</div>
|
|
36
958
|
))}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
959
|
+
{field.variants && field.variants.length > 0 && (
|
|
960
|
+
<div class:list={["space-y-2", hasNestedFields && "pt-1"]}>
|
|
961
|
+
{hasOneOfVariants ? (
|
|
962
|
+
<>
|
|
963
|
+
<p class="text-xs text-neutral-500">
|
|
964
|
+
Select one variant.
|
|
965
|
+
</p>
|
|
966
|
+
<div class="inline-flex max-w-full flex-wrap items-center gap-1 rounded-lg bg-neutral-100 p-1">
|
|
967
|
+
{field.variants.map((variant, variantIndex) => (
|
|
968
|
+
<button
|
|
969
|
+
type="button"
|
|
970
|
+
@click={`selectOneOfVariant(${variantIndex})`}
|
|
971
|
+
:class={`{
|
|
972
|
+
'bg-white text-neutral-900 shadow-xs ring-1 ring-neutral-200': getSelectedOneOfVariant() === ${variantIndex},
|
|
973
|
+
'text-neutral-600 hover:bg-neutral-50 hover:text-neutral-800': getSelectedOneOfVariant() !== ${variantIndex}
|
|
974
|
+
}`}
|
|
975
|
+
class="inline-flex h-7 items-center justify-center whitespace-nowrap rounded-md px-2.5 text-[11px] font-medium transition-all duration-200 cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-300"
|
|
976
|
+
>
|
|
977
|
+
{variant.label}
|
|
978
|
+
</button>
|
|
979
|
+
))}
|
|
980
|
+
</div>
|
|
981
|
+
{field.variants.map((variant, variantIndex) => (
|
|
982
|
+
<div
|
|
983
|
+
x-show={`getSelectedOneOfVariant() === ${variantIndex}`}
|
|
984
|
+
x-cloak
|
|
985
|
+
>
|
|
986
|
+
<div class="rounded-md border border-neutral-200 bg-white p-2.5">
|
|
987
|
+
<div class="mb-2 text-xs font-medium text-neutral-600">
|
|
988
|
+
{variant.label}
|
|
989
|
+
</div>
|
|
990
|
+
<div class="space-y-3">
|
|
991
|
+
{variant.fields.map((variantField) => (
|
|
992
|
+
<div class="border-b border-b-neutral-100 last:border-none pb-3 last:pb-0">
|
|
993
|
+
<Astro.self
|
|
994
|
+
field={variantField}
|
|
995
|
+
requestPart={requestPart}
|
|
996
|
+
scopeExpr={fieldModelExpr}
|
|
997
|
+
defaultsEnabledExpr={`(${resolvedDefaultsEnabledExpr}) && getSelectedOneOfVariant() === ${variantIndex}`}
|
|
998
|
+
/>
|
|
999
|
+
</div>
|
|
1000
|
+
))}
|
|
1001
|
+
</div>
|
|
1002
|
+
</div>
|
|
1003
|
+
</div>
|
|
1004
|
+
))}
|
|
1005
|
+
</>
|
|
1006
|
+
) : (
|
|
1007
|
+
<>
|
|
1008
|
+
<p class="text-xs text-neutral-500">
|
|
1009
|
+
One or more variants may apply.
|
|
1010
|
+
</p>
|
|
1011
|
+
{field.variants.map((variant) => (
|
|
1012
|
+
<div class="rounded-md border border-neutral-200 bg-white p-2.5">
|
|
1013
|
+
<div class="mb-2 text-xs font-medium text-neutral-600">
|
|
1014
|
+
{variant.label}
|
|
1015
|
+
</div>
|
|
1016
|
+
<div class="space-y-3">
|
|
1017
|
+
{variant.fields.map((variantField) => (
|
|
1018
|
+
<div class="border-b border-b-neutral-100 last:border-none pb-3 last:pb-0">
|
|
1019
|
+
<Astro.self
|
|
1020
|
+
field={variantField}
|
|
1021
|
+
requestPart={requestPart}
|
|
1022
|
+
scopeExpr={fieldModelExpr}
|
|
1023
|
+
defaultsEnabledExpr={resolvedDefaultsEnabledExpr}
|
|
1024
|
+
/>
|
|
1025
|
+
</div>
|
|
1026
|
+
))}
|
|
1027
|
+
</div>
|
|
1028
|
+
</div>
|
|
1029
|
+
))}
|
|
1030
|
+
</>
|
|
1031
|
+
)}
|
|
1032
|
+
</div>
|
|
1033
|
+
)}
|
|
1034
|
+
</div>
|
|
42
1035
|
</div>
|
|
43
1036
|
) : (
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
1037
|
+
<>
|
|
1038
|
+
{field.enum && field.enum.length > 0 ? (
|
|
1039
|
+
<div class="relative">
|
|
1040
|
+
<select
|
|
1041
|
+
name={`${requestPart}_${field.name}`}
|
|
1042
|
+
:value="getFieldValue()"
|
|
1043
|
+
@change="setFieldValue($event.target.value, $event.target)"
|
|
1044
|
+
required={isRequiredField}
|
|
1045
|
+
:class="getFieldValue() ? 'text-neutral-700' : 'text-neutral-400'"
|
|
1046
|
+
class="w-full px-3 py-1.5 text-sm border border-neutral-200 focus:border-neutral-300 outline-none rounded-md shadow-xs focus:shadow transition-all duration-200 bg-white appearance-none"
|
|
1047
|
+
>
|
|
1048
|
+
<option value="" disabled hidden>Select value</option>
|
|
1049
|
+
<option value="__clear__">Clear selection</option>
|
|
1050
|
+
{field.enum.map((value) => (
|
|
1051
|
+
<option value={String(value)}>{value}</option>
|
|
1052
|
+
))}
|
|
1053
|
+
</select>
|
|
1054
|
+
<Icon
|
|
1055
|
+
class="absolute right-3 top-1/2 -translate-y-1/2 size-4 pointer-events-none text-neutral-400"
|
|
1056
|
+
name="lucide:chevrons-up-down"
|
|
1057
|
+
/>
|
|
1058
|
+
</div>
|
|
1059
|
+
) : isBooleanType ? (
|
|
1060
|
+
<div class="relative">
|
|
1061
|
+
<select
|
|
1062
|
+
name={`${requestPart}_${field.name}`}
|
|
1063
|
+
:value="getBooleanFieldValue()"
|
|
1064
|
+
@change="setBooleanFieldValue($event.target.value)"
|
|
1065
|
+
required={isRequiredField}
|
|
1066
|
+
:class="getBooleanFieldValue() ? 'text-neutral-700' : 'text-neutral-400'"
|
|
1067
|
+
class="w-full px-3 py-1.5 text-sm border border-neutral-200 focus:border-neutral-300 outline-none rounded-md shadow-xs focus:shadow transition-all duration-200 bg-white appearance-none"
|
|
1068
|
+
>
|
|
1069
|
+
<option value="" disabled hidden>Select value</option>
|
|
1070
|
+
<option value="__clear__">Clear selection</option>
|
|
1071
|
+
<option value="true">true</option>
|
|
1072
|
+
<option value="false">false</option>
|
|
1073
|
+
</select>
|
|
1074
|
+
<Icon
|
|
1075
|
+
class="absolute right-3 top-1/2 -translate-y-1/2 size-4 pointer-events-none text-neutral-400"
|
|
1076
|
+
name="lucide:chevrons-up-down"
|
|
1077
|
+
/>
|
|
1078
|
+
</div>
|
|
1079
|
+
) : (
|
|
1080
|
+
<input
|
|
1081
|
+
name={field.name}
|
|
1082
|
+
placeholder={`Enter ${field.name}`}
|
|
1083
|
+
type={inputType}
|
|
1084
|
+
:value="getFieldValue()"
|
|
1085
|
+
@input="setFieldValue($event.target.value, $event.target)"
|
|
1086
|
+
@blur="reportFieldValidity($event.target)"
|
|
1087
|
+
required={isRequiredField}
|
|
1088
|
+
min={numericInputMin}
|
|
1089
|
+
max={numericInputMax}
|
|
1090
|
+
step={numericInputStep}
|
|
1091
|
+
minlength={textInputMinLength}
|
|
1092
|
+
maxlength={textInputMaxLength}
|
|
1093
|
+
class="w-full px-3 py-1.5 text-sm border border-neutral-200 focus:border-neutral-300 outline-none rounded-md shadow-xs focus:shadow placeholder:text-neutral-400 transition-all duration-200"
|
|
1094
|
+
/>
|
|
1095
|
+
)}
|
|
1096
|
+
</>
|
|
51
1097
|
)
|
|
52
1098
|
}
|
|
53
1099
|
</div>
|