@character-foundry/character-foundry 0.1.3 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -0
- package/dist/app-framework.cjs +1859 -0
- package/dist/app-framework.cjs.map +1 -0
- package/dist/app-framework.d.cts +896 -0
- package/dist/app-framework.d.ts +896 -2
- package/dist/app-framework.js +1835 -1
- package/dist/app-framework.js.map +1 -1
- package/dist/charx.cjs +979 -0
- package/dist/charx.cjs.map +1 -0
- package/dist/charx.d.cts +640 -0
- package/dist/charx.d.ts +640 -2
- package/dist/charx.js +955 -1
- package/dist/charx.js.map +1 -1
- package/dist/core.cjs +755 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.d.cts +404 -0
- package/dist/core.d.ts +404 -2
- package/dist/core.js +731 -1
- package/dist/core.js.map +1 -1
- package/dist/exporter.cjs +7619 -0
- package/dist/exporter.cjs.map +1 -0
- package/dist/exporter.d.cts +681 -0
- package/dist/exporter.d.ts +681 -2
- package/dist/exporter.js +7602 -1
- package/dist/exporter.js.map +1 -1
- package/dist/federation.cjs +3916 -0
- package/dist/federation.cjs.map +1 -0
- package/dist/federation.d.cts +2951 -0
- package/dist/federation.d.ts +2951 -2
- package/dist/federation.js +3892 -1
- package/dist/federation.js.map +1 -1
- package/dist/index.cjs +9213 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1119 -0
- package/dist/index.d.ts +1113 -20
- package/dist/index.js +9196 -26
- package/dist/index.js.map +1 -1
- package/dist/loader.cjs +8951 -0
- package/dist/loader.cjs.map +1 -0
- package/dist/loader.d.cts +1037 -0
- package/dist/loader.d.ts +1037 -2
- package/dist/loader.js +8934 -1
- package/dist/loader.js.map +1 -1
- package/dist/lorebook.cjs +866 -0
- package/dist/lorebook.cjs.map +1 -0
- package/dist/lorebook.d.cts +1008 -0
- package/dist/lorebook.d.ts +1008 -2
- package/dist/lorebook.js +842 -1
- package/dist/lorebook.js.map +1 -1
- package/dist/media.cjs +6661 -0
- package/dist/media.cjs.map +1 -0
- package/dist/media.d.cts +87 -0
- package/dist/media.d.ts +87 -2
- package/dist/media.js +6644 -1
- package/dist/media.js.map +1 -1
- package/dist/normalizer.cjs +503 -0
- package/dist/normalizer.cjs.map +1 -0
- package/dist/normalizer.d.cts +1217 -0
- package/dist/normalizer.d.ts +1217 -2
- package/dist/normalizer.js +479 -1
- package/dist/normalizer.js.map +1 -1
- package/dist/png.cjs +797 -0
- package/dist/png.cjs.map +1 -0
- package/dist/png.d.cts +786 -0
- package/dist/png.d.ts +786 -2
- package/dist/png.js +773 -1
- package/dist/png.js.map +1 -1
- package/dist/schemas.cjs +879 -0
- package/dist/schemas.cjs.map +1 -0
- package/dist/schemas.d.cts +2208 -0
- package/dist/schemas.d.ts +2208 -2
- package/dist/schemas.js +855 -1
- package/dist/schemas.js.map +1 -1
- package/dist/tokenizers.cjs +153 -0
- package/dist/tokenizers.cjs.map +1 -0
- package/dist/tokenizers.d.cts +155 -0
- package/dist/tokenizers.d.ts +155 -2
- package/dist/tokenizers.js +129 -1
- package/dist/tokenizers.js.map +1 -1
- package/dist/voxta.cjs +7907 -0
- package/dist/voxta.cjs.map +1 -0
- package/dist/voxta.d.cts +1349 -0
- package/dist/voxta.d.ts +1349 -2
- package/dist/voxta.js +7890 -1
- package/dist/voxta.js.map +1 -1
- package/package.json +177 -45
- package/dist/app-framework.d.ts.map +0 -1
- package/dist/charx.d.ts.map +0 -1
- package/dist/core.d.ts.map +0 -1
- package/dist/exporter.d.ts.map +0 -1
- package/dist/federation.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/loader.d.ts.map +0 -1
- package/dist/lorebook.d.ts.map +0 -1
- package/dist/media.d.ts.map +0 -1
- package/dist/normalizer.d.ts.map +0 -1
- package/dist/png.d.ts.map +0 -1
- package/dist/schemas.d.ts.map +0 -1
- package/dist/tokenizers.d.ts.map +0 -1
- package/dist/voxta.d.ts.map +0 -1
package/dist/app-framework.js
CHANGED
|
@@ -1,2 +1,1836 @@
|
|
|
1
|
-
|
|
1
|
+
// ../app-framework/dist/index.js
|
|
2
|
+
import { useMemo as useMemo2, useEffect as useEffect2, useCallback as useCallback5, useRef as useRef3, useState as useState5 } from "react";
|
|
3
|
+
import "zod";
|
|
4
|
+
import { useForm, Controller, FormProvider } from "react-hook-form";
|
|
5
|
+
import { zodResolver } from "@hookform/resolvers/zod";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { createContext, useContext } from "react";
|
|
8
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
10
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
11
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
12
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
13
|
+
import { useState, useCallback, useRef, useEffect, useMemo } from "react";
|
|
14
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
15
|
+
import { useState as useState2, useCallback as useCallback2 } from "react";
|
|
16
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
17
|
+
import { useState as useState3, useCallback as useCallback3 } from "react";
|
|
18
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
19
|
+
import { useState as useState4, useCallback as useCallback4, useRef as useRef2 } from "react";
|
|
20
|
+
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
21
|
+
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
22
|
+
import { Fragment, jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
23
|
+
import { useState as useState6, useCallback as useCallback6 } from "react";
|
|
24
|
+
import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
25
|
+
var noopServices = {
|
|
26
|
+
toast: {
|
|
27
|
+
success: () => {
|
|
28
|
+
},
|
|
29
|
+
error: () => {
|
|
30
|
+
},
|
|
31
|
+
info: () => {
|
|
32
|
+
},
|
|
33
|
+
warning: () => {
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
dialog: {
|
|
37
|
+
confirm: async () => false,
|
|
38
|
+
alert: async () => {
|
|
39
|
+
},
|
|
40
|
+
prompt: async () => null
|
|
41
|
+
},
|
|
42
|
+
events: {
|
|
43
|
+
emit: () => {
|
|
44
|
+
},
|
|
45
|
+
on: () => () => {
|
|
46
|
+
},
|
|
47
|
+
once: () => () => {
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
var Registry = class {
|
|
52
|
+
items = /* @__PURE__ */ new Map();
|
|
53
|
+
listeners = /* @__PURE__ */ new Set();
|
|
54
|
+
/**
|
|
55
|
+
* Register an item with the given ID.
|
|
56
|
+
* Overwrites existing item with same ID (with warning).
|
|
57
|
+
*/
|
|
58
|
+
register(id, item) {
|
|
59
|
+
if (this.items.has(id)) {
|
|
60
|
+
console.warn(`Registry: Overwriting existing item with id "${id}"`);
|
|
61
|
+
}
|
|
62
|
+
this.items.set(id, item);
|
|
63
|
+
this.notify(id, item, "register");
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Unregister an item by ID.
|
|
67
|
+
* @returns true if item existed and was removed
|
|
68
|
+
*/
|
|
69
|
+
unregister(id) {
|
|
70
|
+
const existed = this.items.delete(id);
|
|
71
|
+
if (existed) {
|
|
72
|
+
this.notify(id, null, "unregister");
|
|
73
|
+
}
|
|
74
|
+
return existed;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Get an item by ID.
|
|
78
|
+
* @returns The item or undefined if not found
|
|
79
|
+
*/
|
|
80
|
+
get(id) {
|
|
81
|
+
return this.items.get(id);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Check if an item with the given ID exists.
|
|
85
|
+
*/
|
|
86
|
+
has(id) {
|
|
87
|
+
return this.items.has(id);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Get all registered items as a Map.
|
|
91
|
+
* Returns a copy to prevent external modification.
|
|
92
|
+
*/
|
|
93
|
+
getAll() {
|
|
94
|
+
return new Map(this.items);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Get all registered item IDs.
|
|
98
|
+
*/
|
|
99
|
+
getAllIds() {
|
|
100
|
+
return Array.from(this.items.keys());
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Get the number of registered items.
|
|
104
|
+
*/
|
|
105
|
+
get size() {
|
|
106
|
+
return this.items.size;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Subscribe to registry changes.
|
|
110
|
+
* @returns Unsubscribe function
|
|
111
|
+
*/
|
|
112
|
+
subscribe(listener) {
|
|
113
|
+
this.listeners.add(listener);
|
|
114
|
+
return () => this.listeners.delete(listener);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Clear all registered items.
|
|
118
|
+
* Notifies listeners for each removed item.
|
|
119
|
+
*/
|
|
120
|
+
clear() {
|
|
121
|
+
const ids = this.getAllIds();
|
|
122
|
+
this.items.clear();
|
|
123
|
+
ids.forEach((id) => this.notify(id, null, "unregister"));
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Iterate over all items.
|
|
127
|
+
*/
|
|
128
|
+
forEach(callback) {
|
|
129
|
+
this.items.forEach((item, id) => callback(item, id));
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Find items matching a predicate.
|
|
133
|
+
*/
|
|
134
|
+
filter(predicate) {
|
|
135
|
+
const results = [];
|
|
136
|
+
this.items.forEach((item, id) => {
|
|
137
|
+
if (predicate(item, id)) {
|
|
138
|
+
results.push(item);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
return results;
|
|
142
|
+
}
|
|
143
|
+
notify(id, item, action) {
|
|
144
|
+
this.listeners.forEach((listener) => listener(id, item, action));
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
var SettingsRegistry = class extends Registry {
|
|
148
|
+
/**
|
|
149
|
+
* Register a settings panel.
|
|
150
|
+
*/
|
|
151
|
+
registerPanel(panel) {
|
|
152
|
+
super.register(panel.id, panel);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Get all panels sorted by order (lower order = earlier).
|
|
156
|
+
* Hidden panels are excluded.
|
|
157
|
+
*/
|
|
158
|
+
getSortedPanels() {
|
|
159
|
+
return Array.from(this.getAll().values()).filter((panel) => !panel.hidden).sort((a, b) => (a.order ?? 100) - (b.order ?? 100));
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Get a panel by ID with proper typing.
|
|
163
|
+
*/
|
|
164
|
+
getPanel(id) {
|
|
165
|
+
return this.get(id);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
var settingsRegistry = new SettingsRegistry();
|
|
169
|
+
var ProviderRegistry = class extends Registry {
|
|
170
|
+
constructor(providerType) {
|
|
171
|
+
super();
|
|
172
|
+
this.providerType = providerType;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Register a provider.
|
|
176
|
+
*/
|
|
177
|
+
registerProvider(provider) {
|
|
178
|
+
super.register(
|
|
179
|
+
provider.id,
|
|
180
|
+
provider
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Get a provider by ID with proper typing.
|
|
185
|
+
*/
|
|
186
|
+
getProvider(id) {
|
|
187
|
+
return this.get(id);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Create a client instance for a provider.
|
|
191
|
+
* Validates config against schema and runs custom validation if provided.
|
|
192
|
+
*
|
|
193
|
+
* @throws Error if provider not found, config invalid, or custom validation fails
|
|
194
|
+
*/
|
|
195
|
+
async createClient(providerId, config) {
|
|
196
|
+
const provider = this.get(providerId);
|
|
197
|
+
if (!provider) {
|
|
198
|
+
throw new Error(
|
|
199
|
+
`Provider "${providerId}" not found${this.providerType ? ` in ${this.providerType} registry` : ""}`
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
const result = provider.configSchema.safeParse(config);
|
|
203
|
+
if (!result.success) {
|
|
204
|
+
throw new Error(`Invalid provider config: ${result.error.message}`);
|
|
205
|
+
}
|
|
206
|
+
if (provider.validateConfig) {
|
|
207
|
+
const validation = await provider.validateConfig(result.data);
|
|
208
|
+
if (!validation.valid) {
|
|
209
|
+
throw new Error(validation.error ?? "Provider config validation failed");
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return provider.createClient(result.data);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Get all providers as an array.
|
|
216
|
+
*/
|
|
217
|
+
getAllProviders() {
|
|
218
|
+
return Array.from(this.getAll().values());
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
function createProviderRegistry(providerType) {
|
|
222
|
+
return new ProviderRegistry(providerType);
|
|
223
|
+
}
|
|
224
|
+
var WidgetRegistry = class extends Registry {
|
|
225
|
+
/**
|
|
226
|
+
* Register a custom widget.
|
|
227
|
+
*/
|
|
228
|
+
registerWidget(definition) {
|
|
229
|
+
super.register(definition.id, definition);
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Register a widget component directly with just an ID.
|
|
233
|
+
*/
|
|
234
|
+
registerComponent(id, component) {
|
|
235
|
+
super.register(id, { id, component });
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Get a widget component by ID.
|
|
239
|
+
* Returns the component directly, not the definition.
|
|
240
|
+
*/
|
|
241
|
+
getComponent(id) {
|
|
242
|
+
return this.get(id)?.component;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Check if a widget ID is a built-in widget type.
|
|
246
|
+
* This list must stay in sync with the BuiltinWidget type in ui-hints.ts.
|
|
247
|
+
*/
|
|
248
|
+
isBuiltinWidget(id) {
|
|
249
|
+
const builtins = [
|
|
250
|
+
"text",
|
|
251
|
+
"number",
|
|
252
|
+
"password",
|
|
253
|
+
"textarea",
|
|
254
|
+
"switch",
|
|
255
|
+
"checkbox",
|
|
256
|
+
"select",
|
|
257
|
+
"radio",
|
|
258
|
+
"slider",
|
|
259
|
+
"color-picker",
|
|
260
|
+
"tag-input",
|
|
261
|
+
"searchable-select",
|
|
262
|
+
"file-upload"
|
|
263
|
+
];
|
|
264
|
+
return builtins.includes(id);
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
var widgetRegistry = new WidgetRegistry();
|
|
268
|
+
function analyzeSchema(schema, prefix = "") {
|
|
269
|
+
const shape = schema.shape;
|
|
270
|
+
const fields = /* @__PURE__ */ new Map();
|
|
271
|
+
for (const [name, zodType] of Object.entries(shape)) {
|
|
272
|
+
const fullName = prefix ? `${prefix}.${name}` : name;
|
|
273
|
+
fields.set(fullName, analyzeField(fullName, zodType));
|
|
274
|
+
}
|
|
275
|
+
return fields;
|
|
276
|
+
}
|
|
277
|
+
function flattenSchema(schema, prefix = "") {
|
|
278
|
+
const fields = /* @__PURE__ */ new Map();
|
|
279
|
+
const shape = schema.shape;
|
|
280
|
+
for (const [name, zodType] of Object.entries(shape)) {
|
|
281
|
+
const fullName = prefix ? `${prefix}.${name}` : name;
|
|
282
|
+
const fieldInfo = analyzeField(fullName, zodType);
|
|
283
|
+
fields.set(fullName, fieldInfo);
|
|
284
|
+
if (fieldInfo.typeName === "ZodObject" && fieldInfo.innerSchema) {
|
|
285
|
+
const nestedFields = flattenSchema(fieldInfo.innerSchema, fullName);
|
|
286
|
+
for (const [nestedName, nestedInfo] of nestedFields) {
|
|
287
|
+
fields.set(nestedName, nestedInfo);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return fields;
|
|
292
|
+
}
|
|
293
|
+
function analyzeField(name, zodType) {
|
|
294
|
+
let currentType = zodType;
|
|
295
|
+
let isOptional = false;
|
|
296
|
+
let isNullable = false;
|
|
297
|
+
let defaultValue;
|
|
298
|
+
const description = getDescription(zodType);
|
|
299
|
+
if (currentType instanceof z.ZodOptional) {
|
|
300
|
+
isOptional = true;
|
|
301
|
+
currentType = currentType.unwrap();
|
|
302
|
+
}
|
|
303
|
+
if (currentType instanceof z.ZodNullable) {
|
|
304
|
+
isNullable = true;
|
|
305
|
+
currentType = currentType.unwrap();
|
|
306
|
+
}
|
|
307
|
+
if (currentType instanceof z.ZodDefault) {
|
|
308
|
+
defaultValue = currentType._def.defaultValue();
|
|
309
|
+
currentType = currentType._def.innerType;
|
|
310
|
+
}
|
|
311
|
+
if (currentType instanceof z.ZodEffects) {
|
|
312
|
+
currentType = currentType._def.schema;
|
|
313
|
+
}
|
|
314
|
+
if (currentType instanceof z.ZodUnion) {
|
|
315
|
+
const options = currentType._def.options;
|
|
316
|
+
if (options.length > 0 && options[0]) {
|
|
317
|
+
currentType = options[0];
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (currentType instanceof z.ZodDiscriminatedUnion) {
|
|
321
|
+
}
|
|
322
|
+
if (currentType instanceof z.ZodRecord) {
|
|
323
|
+
}
|
|
324
|
+
if (currentType instanceof z.ZodSet) {
|
|
325
|
+
}
|
|
326
|
+
const typeName = currentType.constructor.name;
|
|
327
|
+
let nestedFields;
|
|
328
|
+
let innerSchema;
|
|
329
|
+
if (currentType instanceof z.ZodObject) {
|
|
330
|
+
innerSchema = currentType;
|
|
331
|
+
nestedFields = /* @__PURE__ */ new Map();
|
|
332
|
+
const shape = currentType.shape;
|
|
333
|
+
for (const [childName, childType] of Object.entries(shape)) {
|
|
334
|
+
nestedFields.set(
|
|
335
|
+
childName,
|
|
336
|
+
analyzeField(`${name}.${childName}`, childType)
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
name,
|
|
342
|
+
zodType: currentType,
|
|
343
|
+
typeName,
|
|
344
|
+
isOptional,
|
|
345
|
+
isNullable,
|
|
346
|
+
defaultValue,
|
|
347
|
+
description,
|
|
348
|
+
enumValues: extractEnumValues(currentType),
|
|
349
|
+
innerType: extractInnerType(name, currentType),
|
|
350
|
+
nestedFields,
|
|
351
|
+
innerSchema,
|
|
352
|
+
constraints: extractConstraints(currentType)
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
356
|
+
function isSafeKey(key) {
|
|
357
|
+
return !DANGEROUS_KEYS.has(key);
|
|
358
|
+
}
|
|
359
|
+
function getValueAtPath(obj, path) {
|
|
360
|
+
const parts = path.split(".");
|
|
361
|
+
let current = obj;
|
|
362
|
+
for (const part of parts) {
|
|
363
|
+
if (!isSafeKey(part)) {
|
|
364
|
+
return void 0;
|
|
365
|
+
}
|
|
366
|
+
if (current == null || typeof current !== "object") {
|
|
367
|
+
return void 0;
|
|
368
|
+
}
|
|
369
|
+
if (!Object.hasOwn(current, part)) {
|
|
370
|
+
return void 0;
|
|
371
|
+
}
|
|
372
|
+
current = current[part];
|
|
373
|
+
}
|
|
374
|
+
return current;
|
|
375
|
+
}
|
|
376
|
+
function setValueAtPath(obj, path, value) {
|
|
377
|
+
const parts = path.split(".");
|
|
378
|
+
if (parts.length === 0 || parts[0] === void 0) {
|
|
379
|
+
return obj;
|
|
380
|
+
}
|
|
381
|
+
const first = parts[0];
|
|
382
|
+
if (!isSafeKey(first)) {
|
|
383
|
+
console.warn(`Rejected dangerous property key in path: ${first}`);
|
|
384
|
+
return obj;
|
|
385
|
+
}
|
|
386
|
+
if (parts.length === 1) {
|
|
387
|
+
return { ...obj, [first]: value };
|
|
388
|
+
}
|
|
389
|
+
const rest = parts.slice(1);
|
|
390
|
+
const nested = (Object.hasOwn(obj, first) ? obj[first] : {}) ?? {};
|
|
391
|
+
return {
|
|
392
|
+
...obj,
|
|
393
|
+
[first]: setValueAtPath(nested, rest.join("."), value)
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
function getDescription(zodType) {
|
|
397
|
+
return zodType._def?.description;
|
|
398
|
+
}
|
|
399
|
+
function extractEnumValues(zodType) {
|
|
400
|
+
if (zodType instanceof z.ZodEnum) {
|
|
401
|
+
return zodType._def.values;
|
|
402
|
+
}
|
|
403
|
+
if (zodType instanceof z.ZodNativeEnum) {
|
|
404
|
+
const values = zodType._def.values;
|
|
405
|
+
return Object.values(values).filter(
|
|
406
|
+
(v) => typeof v === "string"
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
return void 0;
|
|
410
|
+
}
|
|
411
|
+
function extractInnerType(name, zodType) {
|
|
412
|
+
if (zodType instanceof z.ZodArray) {
|
|
413
|
+
return analyzeField(`${name}[]`, zodType._def.type);
|
|
414
|
+
}
|
|
415
|
+
if (zodType instanceof z.ZodSet) {
|
|
416
|
+
return analyzeField(`${name}[]`, zodType._def.valueType);
|
|
417
|
+
}
|
|
418
|
+
return void 0;
|
|
419
|
+
}
|
|
420
|
+
function extractConstraints(zodType) {
|
|
421
|
+
const constraints = {};
|
|
422
|
+
const checks = zodType._def?.checks;
|
|
423
|
+
if (!Array.isArray(checks)) {
|
|
424
|
+
return void 0;
|
|
425
|
+
}
|
|
426
|
+
for (const check of checks) {
|
|
427
|
+
switch (check.kind) {
|
|
428
|
+
case "min":
|
|
429
|
+
if (zodType instanceof z.ZodString) {
|
|
430
|
+
constraints.minLength = check.value;
|
|
431
|
+
} else {
|
|
432
|
+
constraints.min = check.value;
|
|
433
|
+
}
|
|
434
|
+
break;
|
|
435
|
+
case "max":
|
|
436
|
+
if (zodType instanceof z.ZodString) {
|
|
437
|
+
constraints.maxLength = check.value;
|
|
438
|
+
} else {
|
|
439
|
+
constraints.max = check.value;
|
|
440
|
+
}
|
|
441
|
+
break;
|
|
442
|
+
case "length":
|
|
443
|
+
constraints.minLength = check.value;
|
|
444
|
+
constraints.maxLength = check.value;
|
|
445
|
+
break;
|
|
446
|
+
case "email":
|
|
447
|
+
constraints.email = true;
|
|
448
|
+
break;
|
|
449
|
+
case "url":
|
|
450
|
+
constraints.url = true;
|
|
451
|
+
break;
|
|
452
|
+
case "uuid":
|
|
453
|
+
constraints.uuid = true;
|
|
454
|
+
break;
|
|
455
|
+
case "regex":
|
|
456
|
+
constraints.regex = check.value;
|
|
457
|
+
break;
|
|
458
|
+
case "int":
|
|
459
|
+
constraints.int = true;
|
|
460
|
+
break;
|
|
461
|
+
case "multipleOf":
|
|
462
|
+
constraints.multipleOf = check.value;
|
|
463
|
+
break;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
if (zodType instanceof z.ZodNumber) {
|
|
467
|
+
const minCheck = checks.find((c) => c.kind === "min");
|
|
468
|
+
if (minCheck && minCheck.inclusive === false && minCheck.value === 0) {
|
|
469
|
+
constraints.positive = true;
|
|
470
|
+
}
|
|
471
|
+
const maxCheck = checks.find((c) => c.kind === "max");
|
|
472
|
+
if (maxCheck && maxCheck.inclusive === false && maxCheck.value === 0) {
|
|
473
|
+
constraints.negative = true;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return Object.keys(constraints).length > 0 ? constraints : void 0;
|
|
477
|
+
}
|
|
478
|
+
function getDefaultWidgetType(fieldInfo) {
|
|
479
|
+
const { typeName, description, enumValues, innerType, constraints } = fieldInfo;
|
|
480
|
+
if (description) {
|
|
481
|
+
const lowerDesc = description.toLowerCase();
|
|
482
|
+
if (lowerDesc.includes("password") || lowerDesc.includes("secret") || lowerDesc.includes("api key") || lowerDesc.includes("apikey") || lowerDesc.includes("token")) {
|
|
483
|
+
return "password";
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
if (constraints?.email) return "text";
|
|
487
|
+
if (constraints?.url) return "text";
|
|
488
|
+
switch (typeName) {
|
|
489
|
+
case "ZodString":
|
|
490
|
+
return "text";
|
|
491
|
+
case "ZodNumber":
|
|
492
|
+
return "number";
|
|
493
|
+
case "ZodBoolean":
|
|
494
|
+
return "switch";
|
|
495
|
+
case "ZodEnum":
|
|
496
|
+
case "ZodNativeEnum":
|
|
497
|
+
if (enumValues) {
|
|
498
|
+
if (enumValues.length <= 4) return "radio";
|
|
499
|
+
if (enumValues.length > 10) return "searchable-select";
|
|
500
|
+
}
|
|
501
|
+
return "select";
|
|
502
|
+
case "ZodArray":
|
|
503
|
+
if (innerType?.typeName === "ZodString") {
|
|
504
|
+
return "tag-input";
|
|
505
|
+
}
|
|
506
|
+
return "text";
|
|
507
|
+
// Fallback
|
|
508
|
+
case "ZodSet":
|
|
509
|
+
if (innerType?.typeName === "ZodString") {
|
|
510
|
+
return "tag-input";
|
|
511
|
+
}
|
|
512
|
+
return "text";
|
|
513
|
+
// Fallback
|
|
514
|
+
case "ZodObject":
|
|
515
|
+
return "nested";
|
|
516
|
+
// Special marker for nested objects
|
|
517
|
+
case "ZodRecord":
|
|
518
|
+
return "text";
|
|
519
|
+
// Records need custom widgets or JSON editor
|
|
520
|
+
case "ZodDiscriminatedUnion":
|
|
521
|
+
return "select";
|
|
522
|
+
// Use discriminator field to select variant
|
|
523
|
+
case "ZodDate":
|
|
524
|
+
return "text";
|
|
525
|
+
// Could be 'date' widget
|
|
526
|
+
default:
|
|
527
|
+
return "text";
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
function isSecretField(fieldInfo) {
|
|
531
|
+
if (!fieldInfo.description) return false;
|
|
532
|
+
const lowerDesc = fieldInfo.description.toLowerCase();
|
|
533
|
+
return lowerDesc.includes("password") || lowerDesc.includes("secret") || lowerDesc.includes("api key") || lowerDesc.includes("apikey") || lowerDesc.includes("token") || lowerDesc.includes("credential");
|
|
534
|
+
}
|
|
535
|
+
function isNestedObject(fieldInfo) {
|
|
536
|
+
return fieldInfo.typeName === "ZodObject" && fieldInfo.nestedFields != null;
|
|
537
|
+
}
|
|
538
|
+
var WidgetRegistryContext = createContext(widgetRegistry);
|
|
539
|
+
function useWidgetRegistry() {
|
|
540
|
+
return useContext(WidgetRegistryContext);
|
|
541
|
+
}
|
|
542
|
+
function TextInput({
|
|
543
|
+
value,
|
|
544
|
+
onChange,
|
|
545
|
+
name,
|
|
546
|
+
label,
|
|
547
|
+
error,
|
|
548
|
+
disabled,
|
|
549
|
+
required,
|
|
550
|
+
hint
|
|
551
|
+
}) {
|
|
552
|
+
const id = `field-${name}`;
|
|
553
|
+
const errorId = `${id}-error`;
|
|
554
|
+
const helperId = `${id}-helper`;
|
|
555
|
+
const hasError = Boolean(error);
|
|
556
|
+
const hasHelper = Boolean(hint?.helperText);
|
|
557
|
+
return /* @__PURE__ */ jsxs("div", { className: hint?.className, "data-field": name, "data-error": hasError, children: [
|
|
558
|
+
label && /* @__PURE__ */ jsxs("label", { htmlFor: id, children: [
|
|
559
|
+
label,
|
|
560
|
+
required && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", "data-required": true, children: "*" })
|
|
561
|
+
] }),
|
|
562
|
+
/* @__PURE__ */ jsx(
|
|
563
|
+
"input",
|
|
564
|
+
{
|
|
565
|
+
id,
|
|
566
|
+
type: "text",
|
|
567
|
+
name,
|
|
568
|
+
value: value ?? "",
|
|
569
|
+
onChange: (e) => onChange(e.target.value),
|
|
570
|
+
disabled,
|
|
571
|
+
placeholder: hint?.placeholder,
|
|
572
|
+
"aria-invalid": hasError,
|
|
573
|
+
"aria-describedby": [hasError && errorId, hasHelper && helperId].filter(Boolean).join(" ") || void 0,
|
|
574
|
+
"aria-required": required,
|
|
575
|
+
readOnly: hint?.readOnly,
|
|
576
|
+
minLength: hint?.min,
|
|
577
|
+
maxLength: hint?.max
|
|
578
|
+
}
|
|
579
|
+
),
|
|
580
|
+
hasHelper && /* @__PURE__ */ jsx("p", { id: helperId, "data-helper": true, children: hint?.helperText }),
|
|
581
|
+
hasError && /* @__PURE__ */ jsx("p", { id: errorId, role: "alert", "data-error-message": true, children: error })
|
|
582
|
+
] });
|
|
583
|
+
}
|
|
584
|
+
function Textarea({
|
|
585
|
+
value,
|
|
586
|
+
onChange,
|
|
587
|
+
name,
|
|
588
|
+
label,
|
|
589
|
+
error,
|
|
590
|
+
disabled,
|
|
591
|
+
required,
|
|
592
|
+
hint
|
|
593
|
+
}) {
|
|
594
|
+
const id = `field-${name}`;
|
|
595
|
+
const errorId = `${id}-error`;
|
|
596
|
+
const helperId = `${id}-helper`;
|
|
597
|
+
const hasError = Boolean(error);
|
|
598
|
+
const hasHelper = Boolean(hint?.helperText);
|
|
599
|
+
const rows = hint?.rows ?? 4;
|
|
600
|
+
return /* @__PURE__ */ jsxs2("div", { className: hint?.className, "data-field": name, "data-error": hasError, children: [
|
|
601
|
+
label && /* @__PURE__ */ jsxs2("label", { htmlFor: id, children: [
|
|
602
|
+
label,
|
|
603
|
+
required && /* @__PURE__ */ jsx2("span", { "aria-hidden": "true", "data-required": true, children: "*" })
|
|
604
|
+
] }),
|
|
605
|
+
/* @__PURE__ */ jsx2(
|
|
606
|
+
"textarea",
|
|
607
|
+
{
|
|
608
|
+
id,
|
|
609
|
+
name,
|
|
610
|
+
value: value ?? "",
|
|
611
|
+
onChange: (e) => onChange(e.target.value),
|
|
612
|
+
disabled,
|
|
613
|
+
readOnly: hint?.readOnly,
|
|
614
|
+
placeholder: hint?.placeholder,
|
|
615
|
+
rows,
|
|
616
|
+
minLength: hint?.min,
|
|
617
|
+
maxLength: hint?.max,
|
|
618
|
+
"aria-invalid": hasError,
|
|
619
|
+
"aria-describedby": [hasError && errorId, hasHelper && helperId].filter(Boolean).join(" ") || void 0,
|
|
620
|
+
"aria-required": required,
|
|
621
|
+
"data-textarea": true
|
|
622
|
+
}
|
|
623
|
+
),
|
|
624
|
+
hasHelper && /* @__PURE__ */ jsx2("p", { id: helperId, "data-helper": true, children: hint?.helperText }),
|
|
625
|
+
hasError && /* @__PURE__ */ jsx2("p", { id: errorId, role: "alert", "data-error-message": true, children: error })
|
|
626
|
+
] });
|
|
627
|
+
}
|
|
628
|
+
function NumberInput({
|
|
629
|
+
value,
|
|
630
|
+
onChange,
|
|
631
|
+
name,
|
|
632
|
+
label,
|
|
633
|
+
error,
|
|
634
|
+
disabled,
|
|
635
|
+
required,
|
|
636
|
+
hint
|
|
637
|
+
}) {
|
|
638
|
+
const id = `field-${name}`;
|
|
639
|
+
const errorId = `${id}-error`;
|
|
640
|
+
const helperId = `${id}-helper`;
|
|
641
|
+
const hasError = Boolean(error);
|
|
642
|
+
const hasHelper = Boolean(hint?.helperText);
|
|
643
|
+
const handleChange = (e) => {
|
|
644
|
+
const val = e.target.value;
|
|
645
|
+
if (val === "") {
|
|
646
|
+
onChange(void 0);
|
|
647
|
+
} else {
|
|
648
|
+
const num = parseFloat(val);
|
|
649
|
+
if (!isNaN(num)) {
|
|
650
|
+
onChange(num);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
return /* @__PURE__ */ jsxs3("div", { className: hint?.className, "data-field": name, "data-error": hasError, children: [
|
|
655
|
+
label && /* @__PURE__ */ jsxs3("label", { htmlFor: id, children: [
|
|
656
|
+
label,
|
|
657
|
+
required && /* @__PURE__ */ jsx3("span", { "aria-hidden": "true", "data-required": true, children: "*" })
|
|
658
|
+
] }),
|
|
659
|
+
/* @__PURE__ */ jsx3(
|
|
660
|
+
"input",
|
|
661
|
+
{
|
|
662
|
+
id,
|
|
663
|
+
type: "number",
|
|
664
|
+
name,
|
|
665
|
+
value: value ?? "",
|
|
666
|
+
onChange: handleChange,
|
|
667
|
+
disabled,
|
|
668
|
+
placeholder: hint?.placeholder,
|
|
669
|
+
"aria-invalid": hasError,
|
|
670
|
+
"aria-describedby": [hasError && errorId, hasHelper && helperId].filter(Boolean).join(" ") || void 0,
|
|
671
|
+
"aria-required": required,
|
|
672
|
+
readOnly: hint?.readOnly,
|
|
673
|
+
min: hint?.min,
|
|
674
|
+
max: hint?.max,
|
|
675
|
+
step: hint?.step
|
|
676
|
+
}
|
|
677
|
+
),
|
|
678
|
+
hasHelper && /* @__PURE__ */ jsx3("p", { id: helperId, "data-helper": true, children: hint?.helperText }),
|
|
679
|
+
hasError && /* @__PURE__ */ jsx3("p", { id: errorId, role: "alert", "data-error-message": true, children: error })
|
|
680
|
+
] });
|
|
681
|
+
}
|
|
682
|
+
function Switch({
|
|
683
|
+
value,
|
|
684
|
+
onChange,
|
|
685
|
+
name,
|
|
686
|
+
label,
|
|
687
|
+
error,
|
|
688
|
+
disabled,
|
|
689
|
+
hint
|
|
690
|
+
}) {
|
|
691
|
+
const id = `field-${name}`;
|
|
692
|
+
const errorId = `${id}-error`;
|
|
693
|
+
const helperId = `${id}-helper`;
|
|
694
|
+
const hasError = Boolean(error);
|
|
695
|
+
const hasHelper = Boolean(hint?.helperText);
|
|
696
|
+
return /* @__PURE__ */ jsxs4("div", { className: hint?.className, "data-field": name, "data-error": hasError, children: [
|
|
697
|
+
/* @__PURE__ */ jsxs4("label", { htmlFor: id, children: [
|
|
698
|
+
/* @__PURE__ */ jsx4(
|
|
699
|
+
"input",
|
|
700
|
+
{
|
|
701
|
+
id,
|
|
702
|
+
type: "checkbox",
|
|
703
|
+
role: "switch",
|
|
704
|
+
name,
|
|
705
|
+
checked: value ?? false,
|
|
706
|
+
onChange: (e) => onChange(e.target.checked),
|
|
707
|
+
disabled: disabled || hint?.readOnly,
|
|
708
|
+
"aria-invalid": hasError,
|
|
709
|
+
"aria-describedby": [hasError && errorId, hasHelper && helperId].filter(Boolean).join(" ") || void 0
|
|
710
|
+
}
|
|
711
|
+
),
|
|
712
|
+
/* @__PURE__ */ jsx4("span", { "data-switch-label": true, children: label })
|
|
713
|
+
] }),
|
|
714
|
+
hasHelper && /* @__PURE__ */ jsx4("p", { id: helperId, "data-helper": true, children: hint?.helperText }),
|
|
715
|
+
hasError && /* @__PURE__ */ jsx4("p", { id: errorId, role: "alert", "data-error-message": true, children: error })
|
|
716
|
+
] });
|
|
717
|
+
}
|
|
718
|
+
function Select({
|
|
719
|
+
value,
|
|
720
|
+
onChange,
|
|
721
|
+
name,
|
|
722
|
+
label,
|
|
723
|
+
error,
|
|
724
|
+
disabled,
|
|
725
|
+
required,
|
|
726
|
+
hint
|
|
727
|
+
}) {
|
|
728
|
+
const id = `field-${name}`;
|
|
729
|
+
const errorId = `${id}-error`;
|
|
730
|
+
const helperId = `${id}-helper`;
|
|
731
|
+
const hasError = Boolean(error);
|
|
732
|
+
const hasHelper = Boolean(hint?.helperText);
|
|
733
|
+
const options = hint?.options ?? [];
|
|
734
|
+
return /* @__PURE__ */ jsxs5("div", { className: hint?.className, "data-field": name, "data-error": hasError, children: [
|
|
735
|
+
label && /* @__PURE__ */ jsxs5("label", { htmlFor: id, children: [
|
|
736
|
+
label,
|
|
737
|
+
required && /* @__PURE__ */ jsx5("span", { "aria-hidden": "true", "data-required": true, children: "*" })
|
|
738
|
+
] }),
|
|
739
|
+
/* @__PURE__ */ jsxs5(
|
|
740
|
+
"select",
|
|
741
|
+
{
|
|
742
|
+
id,
|
|
743
|
+
name,
|
|
744
|
+
value: value ?? "",
|
|
745
|
+
onChange: (e) => onChange(e.target.value),
|
|
746
|
+
disabled: disabled || hint?.readOnly,
|
|
747
|
+
"aria-invalid": hasError,
|
|
748
|
+
"aria-describedby": [hasError && errorId, hasHelper && helperId].filter(Boolean).join(" ") || void 0,
|
|
749
|
+
"aria-required": required,
|
|
750
|
+
children: [
|
|
751
|
+
!required && /* @__PURE__ */ jsx5("option", { value: "", children: "-- Select --" }),
|
|
752
|
+
options.map((opt) => /* @__PURE__ */ jsx5("option", { value: opt.value, children: opt.label }, opt.value))
|
|
753
|
+
]
|
|
754
|
+
}
|
|
755
|
+
),
|
|
756
|
+
hasHelper && /* @__PURE__ */ jsx5("p", { id: helperId, "data-helper": true, children: hint?.helperText }),
|
|
757
|
+
hasError && /* @__PURE__ */ jsx5("p", { id: errorId, role: "alert", "data-error-message": true, children: error })
|
|
758
|
+
] });
|
|
759
|
+
}
|
|
760
|
+
function SearchableSelect({
|
|
761
|
+
value,
|
|
762
|
+
onChange,
|
|
763
|
+
name,
|
|
764
|
+
label,
|
|
765
|
+
error,
|
|
766
|
+
disabled,
|
|
767
|
+
required,
|
|
768
|
+
hint
|
|
769
|
+
}) {
|
|
770
|
+
const id = `field-${name}`;
|
|
771
|
+
const errorId = `${id}-error`;
|
|
772
|
+
const helperId = `${id}-helper`;
|
|
773
|
+
const listboxId = `${id}-listbox`;
|
|
774
|
+
const hasError = Boolean(error);
|
|
775
|
+
const hasHelper = Boolean(hint?.helperText);
|
|
776
|
+
const containerRef = useRef(null);
|
|
777
|
+
const inputRef = useRef(null);
|
|
778
|
+
const listRef = useRef(null);
|
|
779
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
780
|
+
const [searchTerm, setSearchTerm] = useState("");
|
|
781
|
+
const [highlightedIndex, setHighlightedIndex] = useState(-1);
|
|
782
|
+
const rawOptions = hint?.options ?? [];
|
|
783
|
+
const indexedOptions = useMemo(() => {
|
|
784
|
+
return rawOptions.map((opt) => ({
|
|
785
|
+
option: opt,
|
|
786
|
+
labelLower: opt.label.toLowerCase(),
|
|
787
|
+
valueLower: opt.value.toLowerCase()
|
|
788
|
+
}));
|
|
789
|
+
}, [rawOptions]);
|
|
790
|
+
const optionsByValue = useMemo(() => {
|
|
791
|
+
const map = /* @__PURE__ */ new Map();
|
|
792
|
+
for (const opt of rawOptions) {
|
|
793
|
+
map.set(opt.value, opt);
|
|
794
|
+
}
|
|
795
|
+
return map;
|
|
796
|
+
}, [rawOptions]);
|
|
797
|
+
const filteredOptions = useMemo(() => {
|
|
798
|
+
if (!searchTerm) {
|
|
799
|
+
return rawOptions;
|
|
800
|
+
}
|
|
801
|
+
const searchLower = searchTerm.toLowerCase();
|
|
802
|
+
return indexedOptions.filter(
|
|
803
|
+
(indexed) => indexed.labelLower.includes(searchLower) || indexed.valueLower.includes(searchLower)
|
|
804
|
+
).map((indexed) => indexed.option);
|
|
805
|
+
}, [searchTerm, indexedOptions, rawOptions]);
|
|
806
|
+
const selectedOption = optionsByValue.get(value ?? "");
|
|
807
|
+
const displayValue = selectedOption?.label ?? value ?? "";
|
|
808
|
+
useEffect(() => {
|
|
809
|
+
function handleClickOutside(e) {
|
|
810
|
+
if (containerRef.current && !containerRef.current.contains(e.target)) {
|
|
811
|
+
setIsOpen(false);
|
|
812
|
+
setSearchTerm("");
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
816
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
817
|
+
}, []);
|
|
818
|
+
useEffect(() => {
|
|
819
|
+
if (isOpen && highlightedIndex >= 0 && listRef.current) {
|
|
820
|
+
const item = listRef.current.children[highlightedIndex];
|
|
821
|
+
item?.scrollIntoView({ block: "nearest" });
|
|
822
|
+
}
|
|
823
|
+
}, [highlightedIndex, isOpen]);
|
|
824
|
+
const handleOpen = useCallback(() => {
|
|
825
|
+
if (!disabled && !hint?.readOnly) {
|
|
826
|
+
setIsOpen(true);
|
|
827
|
+
setSearchTerm("");
|
|
828
|
+
setHighlightedIndex(-1);
|
|
829
|
+
setTimeout(() => inputRef.current?.focus(), 0);
|
|
830
|
+
}
|
|
831
|
+
}, [disabled, hint?.readOnly]);
|
|
832
|
+
const handleSelect = useCallback(
|
|
833
|
+
(optionValue) => {
|
|
834
|
+
onChange(optionValue);
|
|
835
|
+
setIsOpen(false);
|
|
836
|
+
setSearchTerm("");
|
|
837
|
+
},
|
|
838
|
+
[onChange]
|
|
839
|
+
);
|
|
840
|
+
const handleKeyDown = useCallback(
|
|
841
|
+
(e) => {
|
|
842
|
+
if (disabled || hint?.readOnly) return;
|
|
843
|
+
switch (e.key) {
|
|
844
|
+
case "Enter":
|
|
845
|
+
e.preventDefault();
|
|
846
|
+
if (!isOpen) {
|
|
847
|
+
handleOpen();
|
|
848
|
+
} else if (highlightedIndex >= 0 && filteredOptions[highlightedIndex]) {
|
|
849
|
+
handleSelect(filteredOptions[highlightedIndex].value);
|
|
850
|
+
}
|
|
851
|
+
break;
|
|
852
|
+
case "Escape":
|
|
853
|
+
e.preventDefault();
|
|
854
|
+
setIsOpen(false);
|
|
855
|
+
setSearchTerm("");
|
|
856
|
+
break;
|
|
857
|
+
case "ArrowDown":
|
|
858
|
+
e.preventDefault();
|
|
859
|
+
if (!isOpen) {
|
|
860
|
+
handleOpen();
|
|
861
|
+
} else {
|
|
862
|
+
setHighlightedIndex(
|
|
863
|
+
(prev) => prev < filteredOptions.length - 1 ? prev + 1 : 0
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
break;
|
|
867
|
+
case "ArrowUp":
|
|
868
|
+
e.preventDefault();
|
|
869
|
+
if (isOpen) {
|
|
870
|
+
setHighlightedIndex(
|
|
871
|
+
(prev) => prev > 0 ? prev - 1 : filteredOptions.length - 1
|
|
872
|
+
);
|
|
873
|
+
}
|
|
874
|
+
break;
|
|
875
|
+
case "Home":
|
|
876
|
+
if (isOpen) {
|
|
877
|
+
e.preventDefault();
|
|
878
|
+
setHighlightedIndex(0);
|
|
879
|
+
}
|
|
880
|
+
break;
|
|
881
|
+
case "End":
|
|
882
|
+
if (isOpen) {
|
|
883
|
+
e.preventDefault();
|
|
884
|
+
setHighlightedIndex(filteredOptions.length - 1);
|
|
885
|
+
}
|
|
886
|
+
break;
|
|
887
|
+
}
|
|
888
|
+
},
|
|
889
|
+
[disabled, filteredOptions, handleOpen, handleSelect, highlightedIndex, hint?.readOnly, isOpen]
|
|
890
|
+
);
|
|
891
|
+
const handleSearchChange = useCallback((e) => {
|
|
892
|
+
setSearchTerm(e.target.value);
|
|
893
|
+
setHighlightedIndex(-1);
|
|
894
|
+
}, []);
|
|
895
|
+
const noResultsText = hint?.noResultsText ?? "No results found";
|
|
896
|
+
const searchPlaceholder = hint?.searchPlaceholder ?? "Search...";
|
|
897
|
+
return /* @__PURE__ */ jsxs6(
|
|
898
|
+
"div",
|
|
899
|
+
{
|
|
900
|
+
ref: containerRef,
|
|
901
|
+
className: hint?.className,
|
|
902
|
+
"data-field": name,
|
|
903
|
+
"data-error": hasError,
|
|
904
|
+
onKeyDown: handleKeyDown,
|
|
905
|
+
children: [
|
|
906
|
+
label && /* @__PURE__ */ jsxs6("label", { id: `${id}-label`, children: [
|
|
907
|
+
label,
|
|
908
|
+
required && /* @__PURE__ */ jsx6("span", { "aria-hidden": "true", "data-required": true, children: "*" })
|
|
909
|
+
] }),
|
|
910
|
+
/* @__PURE__ */ jsxs6("div", { "data-searchable-select": true, "data-open": isOpen, "data-disabled": disabled || hint?.readOnly, children: [
|
|
911
|
+
/* @__PURE__ */ jsxs6(
|
|
912
|
+
"button",
|
|
913
|
+
{
|
|
914
|
+
type: "button",
|
|
915
|
+
id,
|
|
916
|
+
onClick: handleOpen,
|
|
917
|
+
disabled,
|
|
918
|
+
"aria-haspopup": "listbox",
|
|
919
|
+
"aria-expanded": isOpen,
|
|
920
|
+
"aria-labelledby": `${id}-label`,
|
|
921
|
+
"aria-describedby": [hasError && errorId, hasHelper && helperId].filter(Boolean).join(" ") || void 0,
|
|
922
|
+
"aria-invalid": hasError,
|
|
923
|
+
"data-searchable-select-trigger": true,
|
|
924
|
+
children: [
|
|
925
|
+
/* @__PURE__ */ jsx6("span", { "data-searchable-select-value": true, children: displayValue || hint?.placeholder || "Select..." }),
|
|
926
|
+
/* @__PURE__ */ jsx6("span", { "data-searchable-select-arrow": true, "aria-hidden": "true", children: isOpen ? "\u25B2" : "\u25BC" })
|
|
927
|
+
]
|
|
928
|
+
}
|
|
929
|
+
),
|
|
930
|
+
isOpen && /* @__PURE__ */ jsxs6("div", { "data-searchable-select-dropdown": true, children: [
|
|
931
|
+
/* @__PURE__ */ jsx6(
|
|
932
|
+
"input",
|
|
933
|
+
{
|
|
934
|
+
ref: inputRef,
|
|
935
|
+
type: "text",
|
|
936
|
+
value: searchTerm,
|
|
937
|
+
onChange: handleSearchChange,
|
|
938
|
+
placeholder: searchPlaceholder,
|
|
939
|
+
"aria-label": "Search options",
|
|
940
|
+
"data-searchable-select-input": true
|
|
941
|
+
}
|
|
942
|
+
),
|
|
943
|
+
/* @__PURE__ */ jsx6(
|
|
944
|
+
"ul",
|
|
945
|
+
{
|
|
946
|
+
ref: listRef,
|
|
947
|
+
id: listboxId,
|
|
948
|
+
role: "listbox",
|
|
949
|
+
"aria-labelledby": `${id}-label`,
|
|
950
|
+
"data-searchable-select-list": true,
|
|
951
|
+
children: filteredOptions.length === 0 ? /* @__PURE__ */ jsx6("li", { "data-searchable-select-no-results": true, children: noResultsText }) : filteredOptions.map((option, index) => /* @__PURE__ */ jsx6(
|
|
952
|
+
"li",
|
|
953
|
+
{
|
|
954
|
+
role: "option",
|
|
955
|
+
"aria-selected": option.value === value,
|
|
956
|
+
"data-searchable-select-option": true,
|
|
957
|
+
"data-selected": option.value === value,
|
|
958
|
+
"data-highlighted": index === highlightedIndex,
|
|
959
|
+
onClick: () => handleSelect(option.value),
|
|
960
|
+
onMouseEnter: () => setHighlightedIndex(index),
|
|
961
|
+
children: option.label
|
|
962
|
+
},
|
|
963
|
+
option.value
|
|
964
|
+
))
|
|
965
|
+
}
|
|
966
|
+
)
|
|
967
|
+
] })
|
|
968
|
+
] }),
|
|
969
|
+
hasHelper && /* @__PURE__ */ jsx6("p", { id: helperId, "data-helper": true, children: hint?.helperText }),
|
|
970
|
+
hasError && /* @__PURE__ */ jsx6("p", { id: errorId, role: "alert", "data-error-message": true, children: error })
|
|
971
|
+
]
|
|
972
|
+
}
|
|
973
|
+
);
|
|
974
|
+
}
|
|
975
|
+
function TagInput({
|
|
976
|
+
value,
|
|
977
|
+
onChange,
|
|
978
|
+
name,
|
|
979
|
+
label,
|
|
980
|
+
error,
|
|
981
|
+
disabled,
|
|
982
|
+
required,
|
|
983
|
+
hint
|
|
984
|
+
}) {
|
|
985
|
+
const id = `field-${name}`;
|
|
986
|
+
const errorId = `${id}-error`;
|
|
987
|
+
const helperId = `${id}-helper`;
|
|
988
|
+
const hasError = Boolean(error);
|
|
989
|
+
const hasHelper = Boolean(hint?.helperText);
|
|
990
|
+
const tags = value ?? [];
|
|
991
|
+
const [inputValue, setInputValue] = useState2("");
|
|
992
|
+
const addTag = useCallback2(
|
|
993
|
+
(tag) => {
|
|
994
|
+
const trimmed = tag.trim();
|
|
995
|
+
if (trimmed && !tags.includes(trimmed)) {
|
|
996
|
+
onChange([...tags, trimmed]);
|
|
997
|
+
}
|
|
998
|
+
setInputValue("");
|
|
999
|
+
},
|
|
1000
|
+
[tags, onChange]
|
|
1001
|
+
);
|
|
1002
|
+
const removeTag = useCallback2(
|
|
1003
|
+
(index) => {
|
|
1004
|
+
onChange(tags.filter((_, i) => i !== index));
|
|
1005
|
+
},
|
|
1006
|
+
[tags, onChange]
|
|
1007
|
+
);
|
|
1008
|
+
const handleKeyDown = useCallback2(
|
|
1009
|
+
(e) => {
|
|
1010
|
+
if (e.key === "Enter" || e.key === ",") {
|
|
1011
|
+
e.preventDefault();
|
|
1012
|
+
addTag(inputValue);
|
|
1013
|
+
} else if (e.key === "Backspace" && inputValue === "" && tags.length > 0) {
|
|
1014
|
+
removeTag(tags.length - 1);
|
|
1015
|
+
}
|
|
1016
|
+
},
|
|
1017
|
+
[inputValue, tags, addTag, removeTag]
|
|
1018
|
+
);
|
|
1019
|
+
const handleBlur = useCallback2(() => {
|
|
1020
|
+
if (inputValue.trim()) {
|
|
1021
|
+
addTag(inputValue);
|
|
1022
|
+
}
|
|
1023
|
+
}, [inputValue, addTag]);
|
|
1024
|
+
return /* @__PURE__ */ jsxs7("div", { className: hint?.className, "data-field": name, "data-error": hasError, children: [
|
|
1025
|
+
label && /* @__PURE__ */ jsxs7("label", { htmlFor: id, children: [
|
|
1026
|
+
label,
|
|
1027
|
+
required && /* @__PURE__ */ jsx7("span", { "aria-hidden": "true", "data-required": true, children: "*" })
|
|
1028
|
+
] }),
|
|
1029
|
+
/* @__PURE__ */ jsxs7("div", { "data-tag-container": true, children: [
|
|
1030
|
+
tags.map((tag, index) => /* @__PURE__ */ jsxs7("span", { "data-tag": true, children: [
|
|
1031
|
+
tag,
|
|
1032
|
+
!disabled && !hint?.readOnly && /* @__PURE__ */ jsx7(
|
|
1033
|
+
"button",
|
|
1034
|
+
{
|
|
1035
|
+
type: "button",
|
|
1036
|
+
onClick: () => removeTag(index),
|
|
1037
|
+
"aria-label": `Remove ${tag}`,
|
|
1038
|
+
"data-tag-remove": true,
|
|
1039
|
+
children: "\xD7"
|
|
1040
|
+
}
|
|
1041
|
+
)
|
|
1042
|
+
] }, `${tag}-${index}`)),
|
|
1043
|
+
/* @__PURE__ */ jsx7(
|
|
1044
|
+
"input",
|
|
1045
|
+
{
|
|
1046
|
+
id,
|
|
1047
|
+
type: "text",
|
|
1048
|
+
value: inputValue,
|
|
1049
|
+
onChange: (e) => setInputValue(e.target.value),
|
|
1050
|
+
onKeyDown: handleKeyDown,
|
|
1051
|
+
onBlur: handleBlur,
|
|
1052
|
+
disabled,
|
|
1053
|
+
readOnly: hint?.readOnly,
|
|
1054
|
+
placeholder: hint?.placeholder ?? "Add tag...",
|
|
1055
|
+
"aria-invalid": hasError,
|
|
1056
|
+
"aria-describedby": [hasError && errorId, hasHelper && helperId].filter(Boolean).join(" ") || void 0,
|
|
1057
|
+
"data-tag-input": true
|
|
1058
|
+
}
|
|
1059
|
+
)
|
|
1060
|
+
] }),
|
|
1061
|
+
hasHelper && /* @__PURE__ */ jsx7("p", { id: helperId, "data-helper": true, children: hint?.helperText }),
|
|
1062
|
+
hasError && /* @__PURE__ */ jsx7("p", { id: errorId, role: "alert", "data-error-message": true, children: error })
|
|
1063
|
+
] });
|
|
1064
|
+
}
|
|
1065
|
+
function SecretInput({
|
|
1066
|
+
value,
|
|
1067
|
+
onChange,
|
|
1068
|
+
name,
|
|
1069
|
+
label,
|
|
1070
|
+
error,
|
|
1071
|
+
disabled,
|
|
1072
|
+
required,
|
|
1073
|
+
hint
|
|
1074
|
+
}) {
|
|
1075
|
+
const id = `field-${name}`;
|
|
1076
|
+
const errorId = `${id}-error`;
|
|
1077
|
+
const helperId = `${id}-helper`;
|
|
1078
|
+
const hasError = Boolean(error);
|
|
1079
|
+
const hasHelper = Boolean(hint?.helperText);
|
|
1080
|
+
const [showValue, setShowValue] = useState3(false);
|
|
1081
|
+
const toggleVisibility = useCallback3(() => {
|
|
1082
|
+
setShowValue((prev) => !prev);
|
|
1083
|
+
}, []);
|
|
1084
|
+
return /* @__PURE__ */ jsxs8("div", { className: hint?.className, "data-field": name, "data-error": hasError, children: [
|
|
1085
|
+
label && /* @__PURE__ */ jsxs8("label", { htmlFor: id, children: [
|
|
1086
|
+
label,
|
|
1087
|
+
required && /* @__PURE__ */ jsx8("span", { "aria-hidden": "true", "data-required": true, children: "*" })
|
|
1088
|
+
] }),
|
|
1089
|
+
/* @__PURE__ */ jsxs8("div", { "data-secret-container": true, children: [
|
|
1090
|
+
/* @__PURE__ */ jsx8(
|
|
1091
|
+
"input",
|
|
1092
|
+
{
|
|
1093
|
+
id,
|
|
1094
|
+
type: showValue ? "text" : "password",
|
|
1095
|
+
name,
|
|
1096
|
+
value: value ?? "",
|
|
1097
|
+
onChange: (e) => onChange(e.target.value),
|
|
1098
|
+
disabled,
|
|
1099
|
+
placeholder: hint?.placeholder,
|
|
1100
|
+
"aria-invalid": hasError,
|
|
1101
|
+
"aria-describedby": [hasError && errorId, hasHelper && helperId].filter(Boolean).join(" ") || void 0,
|
|
1102
|
+
"aria-required": required,
|
|
1103
|
+
readOnly: hint?.readOnly,
|
|
1104
|
+
autoComplete: "off",
|
|
1105
|
+
"data-secret-input": true
|
|
1106
|
+
}
|
|
1107
|
+
),
|
|
1108
|
+
/* @__PURE__ */ jsx8(
|
|
1109
|
+
"button",
|
|
1110
|
+
{
|
|
1111
|
+
type: "button",
|
|
1112
|
+
onClick: toggleVisibility,
|
|
1113
|
+
disabled,
|
|
1114
|
+
"aria-label": showValue ? "Hide value" : "Show value",
|
|
1115
|
+
"data-secret-toggle": true,
|
|
1116
|
+
children: showValue ? "Hide" : "Show"
|
|
1117
|
+
}
|
|
1118
|
+
)
|
|
1119
|
+
] }),
|
|
1120
|
+
hasHelper && /* @__PURE__ */ jsx8("p", { id: helperId, "data-helper": true, children: hint?.helperText }),
|
|
1121
|
+
hasError && /* @__PURE__ */ jsx8("p", { id: errorId, role: "alert", "data-error-message": true, children: error })
|
|
1122
|
+
] });
|
|
1123
|
+
}
|
|
1124
|
+
function FileUpload({
|
|
1125
|
+
value,
|
|
1126
|
+
onChange,
|
|
1127
|
+
name,
|
|
1128
|
+
label,
|
|
1129
|
+
error,
|
|
1130
|
+
disabled,
|
|
1131
|
+
required,
|
|
1132
|
+
hint
|
|
1133
|
+
}) {
|
|
1134
|
+
const id = `field-${name}`;
|
|
1135
|
+
const errorId = `${id}-error`;
|
|
1136
|
+
const helperId = `${id}-helper`;
|
|
1137
|
+
const hasError = Boolean(error);
|
|
1138
|
+
const hasHelper = Boolean(hint?.helperText);
|
|
1139
|
+
const inputRef = useRef2(null);
|
|
1140
|
+
const [isDragging, setIsDragging] = useState4(false);
|
|
1141
|
+
const [localError, setLocalError] = useState4(null);
|
|
1142
|
+
const accept = hint?.accept;
|
|
1143
|
+
const multiple = hint?.multiple ?? false;
|
|
1144
|
+
const maxSize = hint?.maxSize;
|
|
1145
|
+
const validateFiles = useCallback4(
|
|
1146
|
+
(files2) => {
|
|
1147
|
+
const valid = [];
|
|
1148
|
+
const errors = [];
|
|
1149
|
+
for (const file of files2) {
|
|
1150
|
+
if (maxSize && file.size > maxSize) {
|
|
1151
|
+
const maxMB = (maxSize / 1024 / 1024).toFixed(1);
|
|
1152
|
+
errors.push(`${file.name} exceeds ${maxMB}MB limit`);
|
|
1153
|
+
continue;
|
|
1154
|
+
}
|
|
1155
|
+
if (accept) {
|
|
1156
|
+
const acceptedTypes = accept.split(",").map((t) => t.trim());
|
|
1157
|
+
const fileExt = `.${file.name.split(".").pop()?.toLowerCase()}`;
|
|
1158
|
+
const fileMime = file.type;
|
|
1159
|
+
const isAccepted = acceptedTypes.some((type) => {
|
|
1160
|
+
if (type.startsWith(".")) {
|
|
1161
|
+
return fileExt === type.toLowerCase();
|
|
1162
|
+
}
|
|
1163
|
+
if (type.endsWith("/*")) {
|
|
1164
|
+
return fileMime.startsWith(type.replace("/*", "/"));
|
|
1165
|
+
}
|
|
1166
|
+
return fileMime === type;
|
|
1167
|
+
});
|
|
1168
|
+
if (!isAccepted) {
|
|
1169
|
+
errors.push(`${file.name} is not an accepted file type`);
|
|
1170
|
+
continue;
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
valid.push(file);
|
|
1174
|
+
}
|
|
1175
|
+
return { valid, errors };
|
|
1176
|
+
},
|
|
1177
|
+
[accept, maxSize]
|
|
1178
|
+
);
|
|
1179
|
+
const handleFiles = useCallback4(
|
|
1180
|
+
(files2) => {
|
|
1181
|
+
if (!files2 || files2.length === 0) {
|
|
1182
|
+
onChange(multiple ? [] : null);
|
|
1183
|
+
setLocalError(null);
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
const fileArray = Array.from(files2);
|
|
1187
|
+
const { valid, errors } = validateFiles(fileArray);
|
|
1188
|
+
if (errors.length > 0) {
|
|
1189
|
+
setLocalError(errors.join(", "));
|
|
1190
|
+
} else {
|
|
1191
|
+
setLocalError(null);
|
|
1192
|
+
}
|
|
1193
|
+
if (valid.length === 0) {
|
|
1194
|
+
onChange(multiple ? [] : null);
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
if (multiple) {
|
|
1198
|
+
onChange(valid);
|
|
1199
|
+
} else {
|
|
1200
|
+
const firstFile = valid[0];
|
|
1201
|
+
onChange(firstFile ?? null);
|
|
1202
|
+
}
|
|
1203
|
+
},
|
|
1204
|
+
[multiple, onChange, validateFiles]
|
|
1205
|
+
);
|
|
1206
|
+
const handleChange = useCallback4(
|
|
1207
|
+
(e) => {
|
|
1208
|
+
handleFiles(e.target.files);
|
|
1209
|
+
},
|
|
1210
|
+
[handleFiles]
|
|
1211
|
+
);
|
|
1212
|
+
const handleDragOver = useCallback4((e) => {
|
|
1213
|
+
e.preventDefault();
|
|
1214
|
+
e.stopPropagation();
|
|
1215
|
+
setIsDragging(true);
|
|
1216
|
+
}, []);
|
|
1217
|
+
const handleDragLeave = useCallback4((e) => {
|
|
1218
|
+
e.preventDefault();
|
|
1219
|
+
e.stopPropagation();
|
|
1220
|
+
setIsDragging(false);
|
|
1221
|
+
}, []);
|
|
1222
|
+
const handleDrop = useCallback4(
|
|
1223
|
+
(e) => {
|
|
1224
|
+
e.preventDefault();
|
|
1225
|
+
e.stopPropagation();
|
|
1226
|
+
setIsDragging(false);
|
|
1227
|
+
if (disabled || hint?.readOnly) return;
|
|
1228
|
+
handleFiles(e.dataTransfer.files);
|
|
1229
|
+
},
|
|
1230
|
+
[disabled, handleFiles, hint?.readOnly]
|
|
1231
|
+
);
|
|
1232
|
+
const handleClick = useCallback4(() => {
|
|
1233
|
+
if (!disabled && !hint?.readOnly) {
|
|
1234
|
+
inputRef.current?.click();
|
|
1235
|
+
}
|
|
1236
|
+
}, [disabled, hint?.readOnly]);
|
|
1237
|
+
const handleRemove = useCallback4(
|
|
1238
|
+
(index) => {
|
|
1239
|
+
if (multiple && Array.isArray(value)) {
|
|
1240
|
+
if (index !== void 0) {
|
|
1241
|
+
const newFiles = value.filter((_, i) => i !== index);
|
|
1242
|
+
onChange(newFiles);
|
|
1243
|
+
} else {
|
|
1244
|
+
onChange([]);
|
|
1245
|
+
}
|
|
1246
|
+
} else {
|
|
1247
|
+
onChange(null);
|
|
1248
|
+
}
|
|
1249
|
+
setLocalError(null);
|
|
1250
|
+
if (inputRef.current) {
|
|
1251
|
+
inputRef.current.value = "";
|
|
1252
|
+
}
|
|
1253
|
+
},
|
|
1254
|
+
[multiple, onChange, value]
|
|
1255
|
+
);
|
|
1256
|
+
const displayError = error ?? localError;
|
|
1257
|
+
const files = multiple ? Array.isArray(value) ? value : [] : value instanceof File ? [value] : [];
|
|
1258
|
+
return /* @__PURE__ */ jsxs9("div", { className: hint?.className, "data-field": name, "data-error": Boolean(displayError), children: [
|
|
1259
|
+
label && /* @__PURE__ */ jsxs9("label", { htmlFor: id, children: [
|
|
1260
|
+
label,
|
|
1261
|
+
required && /* @__PURE__ */ jsx9("span", { "aria-hidden": "true", "data-required": true, children: "*" })
|
|
1262
|
+
] }),
|
|
1263
|
+
/* @__PURE__ */ jsxs9(
|
|
1264
|
+
"div",
|
|
1265
|
+
{
|
|
1266
|
+
"data-file-dropzone": true,
|
|
1267
|
+
"data-dragging": isDragging,
|
|
1268
|
+
"data-disabled": disabled || hint?.readOnly,
|
|
1269
|
+
onDragOver: handleDragOver,
|
|
1270
|
+
onDragLeave: handleDragLeave,
|
|
1271
|
+
onDrop: handleDrop,
|
|
1272
|
+
onClick: handleClick,
|
|
1273
|
+
role: "button",
|
|
1274
|
+
tabIndex: disabled ? -1 : 0,
|
|
1275
|
+
"aria-describedby": [Boolean(displayError) && errorId, hasHelper && helperId].filter(Boolean).join(" ") || void 0,
|
|
1276
|
+
children: [
|
|
1277
|
+
/* @__PURE__ */ jsx9(
|
|
1278
|
+
"input",
|
|
1279
|
+
{
|
|
1280
|
+
ref: inputRef,
|
|
1281
|
+
id,
|
|
1282
|
+
type: "file",
|
|
1283
|
+
name,
|
|
1284
|
+
accept,
|
|
1285
|
+
multiple,
|
|
1286
|
+
disabled,
|
|
1287
|
+
onChange: handleChange,
|
|
1288
|
+
"aria-invalid": Boolean(displayError),
|
|
1289
|
+
"aria-required": required,
|
|
1290
|
+
"data-file-input": true
|
|
1291
|
+
}
|
|
1292
|
+
),
|
|
1293
|
+
files.length === 0 ? /* @__PURE__ */ jsx9("p", { "data-file-placeholder": true, children: hint?.placeholder ?? (isDragging ? "Drop files here..." : "Click or drag files to upload") }) : /* @__PURE__ */ jsx9("ul", { "data-file-list": true, children: files.map((file, index) => /* @__PURE__ */ jsxs9("li", { "data-file-item": true, children: [
|
|
1294
|
+
/* @__PURE__ */ jsx9("span", { "data-file-name": true, children: file.name }),
|
|
1295
|
+
/* @__PURE__ */ jsxs9("span", { "data-file-size": true, children: [
|
|
1296
|
+
"(",
|
|
1297
|
+
formatFileSize(file.size),
|
|
1298
|
+
")"
|
|
1299
|
+
] }),
|
|
1300
|
+
!disabled && !hint?.readOnly && /* @__PURE__ */ jsx9(
|
|
1301
|
+
"button",
|
|
1302
|
+
{
|
|
1303
|
+
type: "button",
|
|
1304
|
+
onClick: (e) => {
|
|
1305
|
+
e.stopPropagation();
|
|
1306
|
+
handleRemove(multiple ? index : void 0);
|
|
1307
|
+
},
|
|
1308
|
+
"aria-label": `Remove ${file.name}`,
|
|
1309
|
+
"data-file-remove": true,
|
|
1310
|
+
children: "\xD7"
|
|
1311
|
+
}
|
|
1312
|
+
)
|
|
1313
|
+
] }, `${file.name}-${index}`)) })
|
|
1314
|
+
]
|
|
1315
|
+
}
|
|
1316
|
+
),
|
|
1317
|
+
hasHelper && /* @__PURE__ */ jsx9("p", { id: helperId, "data-helper": true, children: hint?.helperText }),
|
|
1318
|
+
displayError && /* @__PURE__ */ jsx9("p", { id: errorId, role: "alert", "data-error-message": true, children: displayError })
|
|
1319
|
+
] });
|
|
1320
|
+
}
|
|
1321
|
+
function formatFileSize(bytes) {
|
|
1322
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
1323
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
1324
|
+
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
1325
|
+
}
|
|
1326
|
+
var BUILTIN_WIDGETS = {
|
|
1327
|
+
text: TextInput,
|
|
1328
|
+
textarea: Textarea,
|
|
1329
|
+
number: NumberInput,
|
|
1330
|
+
switch: Switch,
|
|
1331
|
+
checkbox: Switch,
|
|
1332
|
+
select: Select,
|
|
1333
|
+
"searchable-select": SearchableSelect,
|
|
1334
|
+
radio: Select,
|
|
1335
|
+
// Could be RadioGroup
|
|
1336
|
+
password: SecretInput,
|
|
1337
|
+
"tag-input": TagInput,
|
|
1338
|
+
"file-upload": FileUpload,
|
|
1339
|
+
slider: NumberInput,
|
|
1340
|
+
// Could be Slider widget
|
|
1341
|
+
"color-picker": TextInput
|
|
1342
|
+
// Could be ColorPicker widget
|
|
1343
|
+
};
|
|
1344
|
+
var TYPE_TO_WIDGET = {
|
|
1345
|
+
ZodString: TextInput,
|
|
1346
|
+
ZodNumber: NumberInput,
|
|
1347
|
+
ZodBoolean: Switch,
|
|
1348
|
+
ZodEnum: Select,
|
|
1349
|
+
ZodNativeEnum: Select,
|
|
1350
|
+
ZodArray: TagInput
|
|
1351
|
+
};
|
|
1352
|
+
function FieldRenderer({
|
|
1353
|
+
fieldInfo,
|
|
1354
|
+
hint,
|
|
1355
|
+
value,
|
|
1356
|
+
onChange,
|
|
1357
|
+
error,
|
|
1358
|
+
disabled,
|
|
1359
|
+
renderNestedField
|
|
1360
|
+
}) {
|
|
1361
|
+
const widgetRegistry2 = useWidgetRegistry();
|
|
1362
|
+
if (fieldInfo.typeName === "ZodObject" && fieldInfo.nestedFields && renderNestedField) {
|
|
1363
|
+
const nestedFields = Array.from(fieldInfo.nestedFields.values());
|
|
1364
|
+
const fieldBaseName2 = fieldInfo.name.split(".").pop() ?? fieldInfo.name;
|
|
1365
|
+
return /* @__PURE__ */ jsx10("div", { "data-nested-object": true, "data-field": fieldInfo.name, children: /* @__PURE__ */ jsxs10("fieldset", { "data-nested-fieldset": true, children: [
|
|
1366
|
+
/* @__PURE__ */ jsx10("legend", { "data-nested-legend": true, children: hint?.label ?? fieldInfo.description ?? formatLabel(fieldBaseName2) }),
|
|
1367
|
+
hint?.helperText && /* @__PURE__ */ jsx10("p", { "data-helper": true, children: hint.helperText }),
|
|
1368
|
+
/* @__PURE__ */ jsx10("div", { "data-nested-fields": true, children: nestedFields.map((nestedInfo) => renderNestedField(nestedInfo)) })
|
|
1369
|
+
] }) });
|
|
1370
|
+
}
|
|
1371
|
+
let Widget;
|
|
1372
|
+
if (hint?.widget) {
|
|
1373
|
+
if (typeof hint.widget === "string") {
|
|
1374
|
+
Widget = BUILTIN_WIDGETS[hint.widget] ?? widgetRegistry2.getComponent(hint.widget) ?? TextInput;
|
|
1375
|
+
} else {
|
|
1376
|
+
Widget = hint.widget;
|
|
1377
|
+
}
|
|
1378
|
+
} else {
|
|
1379
|
+
const defaultType = getDefaultWidgetType(fieldInfo);
|
|
1380
|
+
if (fieldInfo.typeName === "ZodString" && hint?.rows && hint.rows > 1) {
|
|
1381
|
+
Widget = Textarea;
|
|
1382
|
+
} else if (defaultType === "searchable-select") {
|
|
1383
|
+
Widget = SearchableSelect;
|
|
1384
|
+
} else {
|
|
1385
|
+
Widget = BUILTIN_WIDGETS[defaultType] ?? TYPE_TO_WIDGET[fieldInfo.typeName] ?? TextInput;
|
|
1386
|
+
}
|
|
1387
|
+
if (fieldInfo.typeName === "ZodString" && isSecretField(fieldInfo)) {
|
|
1388
|
+
Widget = SecretInput;
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
const options = hint?.options ?? (fieldInfo.enumValues ? fieldInfo.enumValues.map((v) => ({ value: v, label: v })) : void 0);
|
|
1392
|
+
const fieldBaseName = fieldInfo.name.split(".").pop() ?? fieldInfo.name;
|
|
1393
|
+
const props = {
|
|
1394
|
+
value,
|
|
1395
|
+
onChange,
|
|
1396
|
+
name: fieldInfo.name,
|
|
1397
|
+
// Full path for form registration
|
|
1398
|
+
label: hint?.label ?? fieldInfo.description ?? formatLabel(fieldBaseName),
|
|
1399
|
+
error,
|
|
1400
|
+
disabled: disabled || hint?.readOnly,
|
|
1401
|
+
required: !fieldInfo.isOptional,
|
|
1402
|
+
hint: {
|
|
1403
|
+
...hint,
|
|
1404
|
+
options,
|
|
1405
|
+
min: hint?.min ?? fieldInfo.constraints?.min ?? fieldInfo.constraints?.minLength,
|
|
1406
|
+
max: hint?.max ?? fieldInfo.constraints?.max ?? fieldInfo.constraints?.maxLength
|
|
1407
|
+
}
|
|
1408
|
+
};
|
|
1409
|
+
return /* @__PURE__ */ jsx10(Widget, { ...props });
|
|
1410
|
+
}
|
|
1411
|
+
function formatLabel(name) {
|
|
1412
|
+
return name.replace(/([A-Z])/g, " $1").replace(/_/g, " ").replace(/^\w/, (c) => c.toUpperCase()).trim();
|
|
1413
|
+
}
|
|
1414
|
+
function AutoForm({
|
|
1415
|
+
schema,
|
|
1416
|
+
values,
|
|
1417
|
+
defaultValues,
|
|
1418
|
+
onChange,
|
|
1419
|
+
onSubmit,
|
|
1420
|
+
uiHints = {},
|
|
1421
|
+
fieldOrder,
|
|
1422
|
+
disabled = false,
|
|
1423
|
+
withSubmit = false,
|
|
1424
|
+
submitText = "Submit",
|
|
1425
|
+
className,
|
|
1426
|
+
widgetRegistry: widgetRegistry2,
|
|
1427
|
+
children
|
|
1428
|
+
}) {
|
|
1429
|
+
const fieldInfoMap = useMemo2(() => analyzeSchema(schema), [schema]);
|
|
1430
|
+
const flatFieldInfoMap = useMemo2(() => flattenSchema(schema), [schema]);
|
|
1431
|
+
const schemaDefaults = useMemo2(() => {
|
|
1432
|
+
const defaults = {};
|
|
1433
|
+
function extractDefaults(fields, prefix = "") {
|
|
1434
|
+
fields.forEach((info, key) => {
|
|
1435
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
1436
|
+
if (info.defaultValue !== void 0) {
|
|
1437
|
+
setNestedValue(defaults, fullKey, info.defaultValue);
|
|
1438
|
+
}
|
|
1439
|
+
if (info.nestedFields) {
|
|
1440
|
+
extractDefaults(info.nestedFields, fullKey);
|
|
1441
|
+
}
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1444
|
+
extractDefaults(fieldInfoMap);
|
|
1445
|
+
return defaults;
|
|
1446
|
+
}, [fieldInfoMap]);
|
|
1447
|
+
const mergedDefaults = useMemo2(() => {
|
|
1448
|
+
return deepMerge(
|
|
1449
|
+
deepMerge(schemaDefaults, defaultValues ?? {}),
|
|
1450
|
+
values ?? {}
|
|
1451
|
+
);
|
|
1452
|
+
}, [schemaDefaults, defaultValues, values]);
|
|
1453
|
+
const methods = useForm({
|
|
1454
|
+
resolver: zodResolver(schema),
|
|
1455
|
+
defaultValues: mergedDefaults,
|
|
1456
|
+
mode: "onChange",
|
|
1457
|
+
shouldUnregister: true
|
|
1458
|
+
});
|
|
1459
|
+
const { control, handleSubmit, watch, formState, reset, getValues } = methods;
|
|
1460
|
+
const prevValuesRef = useRef3(values);
|
|
1461
|
+
const conditionFields = useMemo2(() => {
|
|
1462
|
+
const fields = /* @__PURE__ */ new Set();
|
|
1463
|
+
const extractConditionFields = (hints, prefix = "") => {
|
|
1464
|
+
for (const [key, value] of Object.entries(hints)) {
|
|
1465
|
+
if (!value || typeof value !== "object") continue;
|
|
1466
|
+
const hint = value;
|
|
1467
|
+
if (hint.condition && typeof hint.condition === "object") {
|
|
1468
|
+
const condition = hint.condition;
|
|
1469
|
+
if (condition.field) {
|
|
1470
|
+
fields.add(condition.field);
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
if (!("widget" in hint) && !("label" in hint) && !("condition" in hint)) {
|
|
1474
|
+
extractConditionFields(hint, prefix ? `${prefix}.${key}` : key);
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
};
|
|
1478
|
+
extractConditionFields(uiHints);
|
|
1479
|
+
return Array.from(fields);
|
|
1480
|
+
}, [uiHints]);
|
|
1481
|
+
const [conditionValues, setConditionValues] = useState5({});
|
|
1482
|
+
useEffect2(() => {
|
|
1483
|
+
if (conditionFields.length === 0) return;
|
|
1484
|
+
const initial = {};
|
|
1485
|
+
const currentValues = getValues();
|
|
1486
|
+
for (const field of conditionFields) {
|
|
1487
|
+
initial[field] = getValueAtPath(currentValues, field);
|
|
1488
|
+
}
|
|
1489
|
+
setConditionValues(initial);
|
|
1490
|
+
const subscription = watch((formValues, { name }) => {
|
|
1491
|
+
if (name && conditionFields.includes(name)) {
|
|
1492
|
+
setConditionValues((prev) => ({
|
|
1493
|
+
...prev,
|
|
1494
|
+
[name]: getValueAtPath(formValues, name)
|
|
1495
|
+
}));
|
|
1496
|
+
}
|
|
1497
|
+
});
|
|
1498
|
+
return () => subscription.unsubscribe();
|
|
1499
|
+
}, [conditionFields, watch, getValues]);
|
|
1500
|
+
useEffect2(() => {
|
|
1501
|
+
if (values && !shallowEqual(values, prevValuesRef.current)) {
|
|
1502
|
+
prevValuesRef.current = values;
|
|
1503
|
+
const resetValues = deepMerge(
|
|
1504
|
+
deepMerge(schemaDefaults, defaultValues ?? {}),
|
|
1505
|
+
values ?? {}
|
|
1506
|
+
);
|
|
1507
|
+
reset(resetValues);
|
|
1508
|
+
}
|
|
1509
|
+
}, [values, reset, schemaDefaults, defaultValues]);
|
|
1510
|
+
const onChangeRef = useRef3(onChange);
|
|
1511
|
+
onChangeRef.current = onChange;
|
|
1512
|
+
const schemaRef = useRef3(schema);
|
|
1513
|
+
schemaRef.current = schema;
|
|
1514
|
+
useEffect2(() => {
|
|
1515
|
+
if (!onChangeRef.current) return;
|
|
1516
|
+
const subscription = watch((formValues, { type }) => {
|
|
1517
|
+
if (type !== "change") return;
|
|
1518
|
+
const result = schemaRef.current.safeParse(formValues);
|
|
1519
|
+
if (result.success && onChangeRef.current) {
|
|
1520
|
+
onChangeRef.current(result.data);
|
|
1521
|
+
}
|
|
1522
|
+
});
|
|
1523
|
+
return () => subscription.unsubscribe();
|
|
1524
|
+
}, [watch]);
|
|
1525
|
+
const HINT_KEYS = /* @__PURE__ */ new Set([
|
|
1526
|
+
"widget",
|
|
1527
|
+
"label",
|
|
1528
|
+
"placeholder",
|
|
1529
|
+
"helperText",
|
|
1530
|
+
"hidden",
|
|
1531
|
+
"readOnly",
|
|
1532
|
+
"className",
|
|
1533
|
+
"condition",
|
|
1534
|
+
"group",
|
|
1535
|
+
"rows",
|
|
1536
|
+
"accept",
|
|
1537
|
+
"multiple",
|
|
1538
|
+
"maxSize",
|
|
1539
|
+
"options",
|
|
1540
|
+
"searchable",
|
|
1541
|
+
"searchPlaceholder",
|
|
1542
|
+
"noResultsText"
|
|
1543
|
+
]);
|
|
1544
|
+
const isFieldUIHint = useCallback5((obj) => {
|
|
1545
|
+
if (!obj || typeof obj !== "object") return false;
|
|
1546
|
+
return Object.keys(obj).some((key) => HINT_KEYS.has(key));
|
|
1547
|
+
}, []);
|
|
1548
|
+
const getHint = useCallback5(
|
|
1549
|
+
(fieldName) => {
|
|
1550
|
+
if (fieldName in uiHints) {
|
|
1551
|
+
const hint = uiHints[fieldName];
|
|
1552
|
+
if (isFieldUIHint(hint)) return hint;
|
|
1553
|
+
}
|
|
1554
|
+
const parts = fieldName.split(".");
|
|
1555
|
+
let current = uiHints;
|
|
1556
|
+
for (const part of parts) {
|
|
1557
|
+
if (current == null || typeof current !== "object") return void 0;
|
|
1558
|
+
current = current[part];
|
|
1559
|
+
}
|
|
1560
|
+
if (isFieldUIHint(current)) {
|
|
1561
|
+
return current;
|
|
1562
|
+
}
|
|
1563
|
+
return void 0;
|
|
1564
|
+
},
|
|
1565
|
+
[uiHints, isFieldUIHint]
|
|
1566
|
+
);
|
|
1567
|
+
const isConditionMet = useCallback5(
|
|
1568
|
+
(condition) => {
|
|
1569
|
+
if (!condition) return true;
|
|
1570
|
+
const fieldValue = conditionFields.length > 0 ? conditionValues[condition.field] : getValueAtPath(getValues(), condition.field);
|
|
1571
|
+
if (condition.when) {
|
|
1572
|
+
return condition.when(fieldValue, getValues());
|
|
1573
|
+
}
|
|
1574
|
+
if ("equals" in condition && condition.equals !== void 0) {
|
|
1575
|
+
return fieldValue === condition.equals;
|
|
1576
|
+
}
|
|
1577
|
+
if ("notEquals" in condition && condition.notEquals !== void 0) {
|
|
1578
|
+
return fieldValue !== condition.notEquals;
|
|
1579
|
+
}
|
|
1580
|
+
if (condition.oneOf) {
|
|
1581
|
+
return condition.oneOf.includes(fieldValue);
|
|
1582
|
+
}
|
|
1583
|
+
if (condition.notOneOf) {
|
|
1584
|
+
return !condition.notOneOf.includes(fieldValue);
|
|
1585
|
+
}
|
|
1586
|
+
return true;
|
|
1587
|
+
},
|
|
1588
|
+
[conditionFields, conditionValues, getValues]
|
|
1589
|
+
);
|
|
1590
|
+
const orderedFields = useMemo2(() => {
|
|
1591
|
+
if (fieldOrder) {
|
|
1592
|
+
return fieldOrder.map((f) => String(f));
|
|
1593
|
+
}
|
|
1594
|
+
return Array.from(fieldInfoMap.keys());
|
|
1595
|
+
}, [fieldOrder, fieldInfoMap]);
|
|
1596
|
+
const onFormSubmit = handleSubmit(async (data) => {
|
|
1597
|
+
await onSubmit?.(data);
|
|
1598
|
+
});
|
|
1599
|
+
const renderField = useCallback5(
|
|
1600
|
+
(fieldInfo) => {
|
|
1601
|
+
const hint = getHint(fieldInfo.name);
|
|
1602
|
+
if (hint?.hidden) return null;
|
|
1603
|
+
if (!isConditionMet(hint?.condition)) return null;
|
|
1604
|
+
return /* @__PURE__ */ jsx11(
|
|
1605
|
+
Controller,
|
|
1606
|
+
{
|
|
1607
|
+
name: fieldInfo.name,
|
|
1608
|
+
control,
|
|
1609
|
+
render: ({ field, fieldState }) => /* @__PURE__ */ jsx11(
|
|
1610
|
+
FieldRenderer,
|
|
1611
|
+
{
|
|
1612
|
+
fieldInfo,
|
|
1613
|
+
hint,
|
|
1614
|
+
value: field.value,
|
|
1615
|
+
onChange: field.onChange,
|
|
1616
|
+
error: fieldState.error?.message,
|
|
1617
|
+
disabled,
|
|
1618
|
+
renderNestedField: renderField
|
|
1619
|
+
}
|
|
1620
|
+
)
|
|
1621
|
+
},
|
|
1622
|
+
fieldInfo.name
|
|
1623
|
+
);
|
|
1624
|
+
},
|
|
1625
|
+
[control, disabled, getHint, isConditionMet]
|
|
1626
|
+
);
|
|
1627
|
+
const renderedFields = orderedFields.map((fieldName) => {
|
|
1628
|
+
const fieldInfo = fieldInfoMap.get(fieldName);
|
|
1629
|
+
if (!fieldInfo) return null;
|
|
1630
|
+
return renderField(fieldInfo);
|
|
1631
|
+
}).filter(Boolean);
|
|
1632
|
+
const getField = useCallback5(
|
|
1633
|
+
(name) => {
|
|
1634
|
+
const fieldInfo = flatFieldInfoMap.get(name);
|
|
1635
|
+
if (!fieldInfo) return null;
|
|
1636
|
+
return renderField(fieldInfo);
|
|
1637
|
+
},
|
|
1638
|
+
[flatFieldInfoMap, renderField]
|
|
1639
|
+
);
|
|
1640
|
+
const getFieldsByGroup = useCallback5(
|
|
1641
|
+
(group) => {
|
|
1642
|
+
const fields = [];
|
|
1643
|
+
for (const [name] of flatFieldInfoMap) {
|
|
1644
|
+
const hint = getHint(name);
|
|
1645
|
+
if (hint?.group === group) {
|
|
1646
|
+
const rendered = getField(name);
|
|
1647
|
+
if (rendered) fields.push(rendered);
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
return fields;
|
|
1651
|
+
},
|
|
1652
|
+
[flatFieldInfoMap, getField, getHint]
|
|
1653
|
+
);
|
|
1654
|
+
const submitButton = withSubmit ? /* @__PURE__ */ jsx11(
|
|
1655
|
+
"button",
|
|
1656
|
+
{
|
|
1657
|
+
type: "submit",
|
|
1658
|
+
disabled: disabled || formState.isSubmitting,
|
|
1659
|
+
"data-autoform-submit": true,
|
|
1660
|
+
children: formState.isSubmitting ? "Submitting..." : submitText
|
|
1661
|
+
}
|
|
1662
|
+
) : null;
|
|
1663
|
+
const formContent = /* @__PURE__ */ jsx11(FormProvider, { ...methods, children: /* @__PURE__ */ jsx11("form", { onSubmit: onFormSubmit, className, "data-autoform": true, children: children ? children({
|
|
1664
|
+
fields: renderedFields,
|
|
1665
|
+
submit: submitButton,
|
|
1666
|
+
formState: {
|
|
1667
|
+
isSubmitting: formState.isSubmitting,
|
|
1668
|
+
isValid: formState.isValid,
|
|
1669
|
+
isDirty: formState.isDirty
|
|
1670
|
+
},
|
|
1671
|
+
getField,
|
|
1672
|
+
getFieldsByGroup
|
|
1673
|
+
}) : /* @__PURE__ */ jsxs11(Fragment, { children: [
|
|
1674
|
+
renderedFields,
|
|
1675
|
+
submitButton
|
|
1676
|
+
] }) }) });
|
|
1677
|
+
if (widgetRegistry2) {
|
|
1678
|
+
return /* @__PURE__ */ jsx11(WidgetRegistryContext.Provider, { value: widgetRegistry2, children: formContent });
|
|
1679
|
+
}
|
|
1680
|
+
return formContent;
|
|
1681
|
+
}
|
|
1682
|
+
function deepMerge(base, override) {
|
|
1683
|
+
const result = { ...base };
|
|
1684
|
+
for (const key of Object.keys(override)) {
|
|
1685
|
+
if (!isSafeKey2(key)) continue;
|
|
1686
|
+
const baseVal = base[key];
|
|
1687
|
+
const overrideVal = override[key];
|
|
1688
|
+
if (isPlainObject(baseVal) && isPlainObject(overrideVal)) {
|
|
1689
|
+
result[key] = deepMerge(
|
|
1690
|
+
baseVal,
|
|
1691
|
+
overrideVal
|
|
1692
|
+
);
|
|
1693
|
+
} else if (overrideVal !== void 0) {
|
|
1694
|
+
result[key] = overrideVal;
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
return result;
|
|
1698
|
+
}
|
|
1699
|
+
function isPlainObject(val) {
|
|
1700
|
+
return val !== null && typeof val === "object" && !Array.isArray(val) && Object.getPrototypeOf(val) === Object.prototype;
|
|
1701
|
+
}
|
|
1702
|
+
var DANGEROUS_KEYS2 = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
1703
|
+
function isSafeKey2(key) {
|
|
1704
|
+
return !DANGEROUS_KEYS2.has(key);
|
|
1705
|
+
}
|
|
1706
|
+
function setNestedValue(obj, path, value) {
|
|
1707
|
+
const parts = path.split(".");
|
|
1708
|
+
let current = obj;
|
|
1709
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
1710
|
+
const part = parts[i];
|
|
1711
|
+
if (part === void 0) continue;
|
|
1712
|
+
if (!isSafeKey2(part)) {
|
|
1713
|
+
console.warn(`Rejected dangerous property key in path: ${part}`);
|
|
1714
|
+
return;
|
|
1715
|
+
}
|
|
1716
|
+
if (!Object.hasOwn(current, part) || typeof current[part] !== "object") {
|
|
1717
|
+
current[part] = {};
|
|
1718
|
+
}
|
|
1719
|
+
current = current[part];
|
|
1720
|
+
}
|
|
1721
|
+
const lastPart = parts[parts.length - 1];
|
|
1722
|
+
if (lastPart !== void 0) {
|
|
1723
|
+
if (!isSafeKey2(lastPart)) {
|
|
1724
|
+
console.warn(`Rejected dangerous property key in path: ${lastPart}`);
|
|
1725
|
+
return;
|
|
1726
|
+
}
|
|
1727
|
+
current[lastPart] = value;
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
function shallowEqual(a, b) {
|
|
1731
|
+
if (a === b) return true;
|
|
1732
|
+
if (a == null || b == null) return false;
|
|
1733
|
+
if (typeof a !== "object" || typeof b !== "object") return a === b;
|
|
1734
|
+
const keysA = Object.keys(a);
|
|
1735
|
+
const keysB = Object.keys(b);
|
|
1736
|
+
if (keysA.length !== keysB.length) return false;
|
|
1737
|
+
for (const key of keysA) {
|
|
1738
|
+
const valA = a[key];
|
|
1739
|
+
const valB = b[key];
|
|
1740
|
+
if (typeof valA === "object" && typeof valB === "object" && valA !== null && valB !== null) {
|
|
1741
|
+
if (!shallowEqual(valA, valB)) return false;
|
|
1742
|
+
} else if (valA !== valB) {
|
|
1743
|
+
return false;
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
return true;
|
|
1747
|
+
}
|
|
1748
|
+
function FieldGroup({
|
|
1749
|
+
title,
|
|
1750
|
+
description,
|
|
1751
|
+
collapsible = false,
|
|
1752
|
+
defaultCollapsed = false,
|
|
1753
|
+
className,
|
|
1754
|
+
children
|
|
1755
|
+
}) {
|
|
1756
|
+
const [isCollapsed, setIsCollapsed] = useState6(defaultCollapsed);
|
|
1757
|
+
const toggleCollapsed = useCallback6(() => {
|
|
1758
|
+
if (collapsible) {
|
|
1759
|
+
setIsCollapsed((prev) => !prev);
|
|
1760
|
+
}
|
|
1761
|
+
}, [collapsible]);
|
|
1762
|
+
const headerId = `fieldgroup-${title.toLowerCase().replace(/\s+/g, "-")}`;
|
|
1763
|
+
const contentId = `${headerId}-content`;
|
|
1764
|
+
return /* @__PURE__ */ jsxs12(
|
|
1765
|
+
"fieldset",
|
|
1766
|
+
{
|
|
1767
|
+
className,
|
|
1768
|
+
"data-fieldgroup": true,
|
|
1769
|
+
"data-collapsible": collapsible,
|
|
1770
|
+
"data-collapsed": isCollapsed,
|
|
1771
|
+
children: [
|
|
1772
|
+
collapsible ? /* @__PURE__ */ jsx12("legend", { children: /* @__PURE__ */ jsxs12(
|
|
1773
|
+
"button",
|
|
1774
|
+
{
|
|
1775
|
+
type: "button",
|
|
1776
|
+
onClick: toggleCollapsed,
|
|
1777
|
+
"aria-expanded": !isCollapsed,
|
|
1778
|
+
"aria-controls": contentId,
|
|
1779
|
+
"data-fieldgroup-toggle": true,
|
|
1780
|
+
children: [
|
|
1781
|
+
/* @__PURE__ */ jsx12("span", { "data-fieldgroup-arrow": true, "aria-hidden": "true", children: isCollapsed ? "\u25B6" : "\u25BC" }),
|
|
1782
|
+
/* @__PURE__ */ jsx12("span", { id: headerId, "data-fieldgroup-title": true, children: title })
|
|
1783
|
+
]
|
|
1784
|
+
}
|
|
1785
|
+
) }) : /* @__PURE__ */ jsx12("legend", { id: headerId, "data-fieldgroup-title": true, children: title }),
|
|
1786
|
+
description && /* @__PURE__ */ jsx12("p", { "data-fieldgroup-description": true, children: description }),
|
|
1787
|
+
/* @__PURE__ */ jsx12(
|
|
1788
|
+
"div",
|
|
1789
|
+
{
|
|
1790
|
+
id: contentId,
|
|
1791
|
+
role: "group",
|
|
1792
|
+
"aria-labelledby": headerId,
|
|
1793
|
+
"data-fieldgroup-content": true,
|
|
1794
|
+
hidden: collapsible && isCollapsed,
|
|
1795
|
+
children
|
|
1796
|
+
}
|
|
1797
|
+
)
|
|
1798
|
+
]
|
|
1799
|
+
}
|
|
1800
|
+
);
|
|
1801
|
+
}
|
|
1802
|
+
var FieldSection = FieldGroup;
|
|
1803
|
+
export {
|
|
1804
|
+
AutoForm,
|
|
1805
|
+
FieldGroup,
|
|
1806
|
+
FieldRenderer,
|
|
1807
|
+
FieldSection,
|
|
1808
|
+
FileUpload,
|
|
1809
|
+
NumberInput,
|
|
1810
|
+
ProviderRegistry,
|
|
1811
|
+
Registry,
|
|
1812
|
+
SearchableSelect,
|
|
1813
|
+
SecretInput,
|
|
1814
|
+
Select,
|
|
1815
|
+
SettingsRegistry,
|
|
1816
|
+
Switch,
|
|
1817
|
+
TagInput,
|
|
1818
|
+
TextInput,
|
|
1819
|
+
Textarea,
|
|
1820
|
+
WidgetRegistry,
|
|
1821
|
+
WidgetRegistryContext,
|
|
1822
|
+
analyzeField,
|
|
1823
|
+
analyzeSchema,
|
|
1824
|
+
createProviderRegistry,
|
|
1825
|
+
flattenSchema,
|
|
1826
|
+
getDefaultWidgetType,
|
|
1827
|
+
getValueAtPath,
|
|
1828
|
+
isNestedObject,
|
|
1829
|
+
isSecretField,
|
|
1830
|
+
noopServices,
|
|
1831
|
+
setValueAtPath,
|
|
1832
|
+
settingsRegistry,
|
|
1833
|
+
useWidgetRegistry,
|
|
1834
|
+
widgetRegistry
|
|
1835
|
+
};
|
|
2
1836
|
//# sourceMappingURL=app-framework.js.map
|