@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.
Files changed (100) hide show
  1. package/README.md +70 -0
  2. package/dist/app-framework.cjs +1859 -0
  3. package/dist/app-framework.cjs.map +1 -0
  4. package/dist/app-framework.d.cts +896 -0
  5. package/dist/app-framework.d.ts +896 -2
  6. package/dist/app-framework.js +1835 -1
  7. package/dist/app-framework.js.map +1 -1
  8. package/dist/charx.cjs +979 -0
  9. package/dist/charx.cjs.map +1 -0
  10. package/dist/charx.d.cts +640 -0
  11. package/dist/charx.d.ts +640 -2
  12. package/dist/charx.js +955 -1
  13. package/dist/charx.js.map +1 -1
  14. package/dist/core.cjs +755 -0
  15. package/dist/core.cjs.map +1 -0
  16. package/dist/core.d.cts +404 -0
  17. package/dist/core.d.ts +404 -2
  18. package/dist/core.js +731 -1
  19. package/dist/core.js.map +1 -1
  20. package/dist/exporter.cjs +7619 -0
  21. package/dist/exporter.cjs.map +1 -0
  22. package/dist/exporter.d.cts +681 -0
  23. package/dist/exporter.d.ts +681 -2
  24. package/dist/exporter.js +7602 -1
  25. package/dist/exporter.js.map +1 -1
  26. package/dist/federation.cjs +3916 -0
  27. package/dist/federation.cjs.map +1 -0
  28. package/dist/federation.d.cts +2951 -0
  29. package/dist/federation.d.ts +2951 -2
  30. package/dist/federation.js +3892 -1
  31. package/dist/federation.js.map +1 -1
  32. package/dist/index.cjs +9213 -0
  33. package/dist/index.cjs.map +1 -0
  34. package/dist/index.d.cts +1119 -0
  35. package/dist/index.d.ts +1113 -20
  36. package/dist/index.js +9196 -26
  37. package/dist/index.js.map +1 -1
  38. package/dist/loader.cjs +8951 -0
  39. package/dist/loader.cjs.map +1 -0
  40. package/dist/loader.d.cts +1037 -0
  41. package/dist/loader.d.ts +1037 -2
  42. package/dist/loader.js +8934 -1
  43. package/dist/loader.js.map +1 -1
  44. package/dist/lorebook.cjs +866 -0
  45. package/dist/lorebook.cjs.map +1 -0
  46. package/dist/lorebook.d.cts +1008 -0
  47. package/dist/lorebook.d.ts +1008 -2
  48. package/dist/lorebook.js +842 -1
  49. package/dist/lorebook.js.map +1 -1
  50. package/dist/media.cjs +6661 -0
  51. package/dist/media.cjs.map +1 -0
  52. package/dist/media.d.cts +87 -0
  53. package/dist/media.d.ts +87 -2
  54. package/dist/media.js +6644 -1
  55. package/dist/media.js.map +1 -1
  56. package/dist/normalizer.cjs +503 -0
  57. package/dist/normalizer.cjs.map +1 -0
  58. package/dist/normalizer.d.cts +1217 -0
  59. package/dist/normalizer.d.ts +1217 -2
  60. package/dist/normalizer.js +479 -1
  61. package/dist/normalizer.js.map +1 -1
  62. package/dist/png.cjs +797 -0
  63. package/dist/png.cjs.map +1 -0
  64. package/dist/png.d.cts +786 -0
  65. package/dist/png.d.ts +786 -2
  66. package/dist/png.js +773 -1
  67. package/dist/png.js.map +1 -1
  68. package/dist/schemas.cjs +879 -0
  69. package/dist/schemas.cjs.map +1 -0
  70. package/dist/schemas.d.cts +2208 -0
  71. package/dist/schemas.d.ts +2208 -2
  72. package/dist/schemas.js +855 -1
  73. package/dist/schemas.js.map +1 -1
  74. package/dist/tokenizers.cjs +153 -0
  75. package/dist/tokenizers.cjs.map +1 -0
  76. package/dist/tokenizers.d.cts +155 -0
  77. package/dist/tokenizers.d.ts +155 -2
  78. package/dist/tokenizers.js +129 -1
  79. package/dist/tokenizers.js.map +1 -1
  80. package/dist/voxta.cjs +7907 -0
  81. package/dist/voxta.cjs.map +1 -0
  82. package/dist/voxta.d.cts +1349 -0
  83. package/dist/voxta.d.ts +1349 -2
  84. package/dist/voxta.js +7890 -1
  85. package/dist/voxta.js.map +1 -1
  86. package/package.json +177 -45
  87. package/dist/app-framework.d.ts.map +0 -1
  88. package/dist/charx.d.ts.map +0 -1
  89. package/dist/core.d.ts.map +0 -1
  90. package/dist/exporter.d.ts.map +0 -1
  91. package/dist/federation.d.ts.map +0 -1
  92. package/dist/index.d.ts.map +0 -1
  93. package/dist/loader.d.ts.map +0 -1
  94. package/dist/lorebook.d.ts.map +0 -1
  95. package/dist/media.d.ts.map +0 -1
  96. package/dist/normalizer.d.ts.map +0 -1
  97. package/dist/png.d.ts.map +0 -1
  98. package/dist/schemas.d.ts.map +0 -1
  99. package/dist/tokenizers.d.ts.map +0 -1
  100. package/dist/voxta.d.ts.map +0 -1
@@ -1,2 +1,1836 @@
1
- export * from '@character-foundry/app-framework';
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