@embeddables/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/README.md +116 -0
  2. package/bin/embeddables.mjs +2 -0
  3. package/dist/auth/index.d.ts +43 -0
  4. package/dist/auth/index.d.ts.map +1 -0
  5. package/dist/auth/index.js +100 -0
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +75 -0
  9. package/dist/commands/build-workbench.d.ts +5 -0
  10. package/dist/commands/build-workbench.d.ts.map +1 -0
  11. package/dist/commands/build-workbench.js +122 -0
  12. package/dist/commands/build.d.ts +7 -0
  13. package/dist/commands/build.d.ts.map +1 -0
  14. package/dist/commands/build.js +22 -0
  15. package/dist/commands/dev.d.ts +11 -0
  16. package/dist/commands/dev.d.ts.map +1 -0
  17. package/dist/commands/dev.js +153 -0
  18. package/dist/commands/login.d.ts +2 -0
  19. package/dist/commands/login.d.ts.map +1 -0
  20. package/dist/commands/login.js +112 -0
  21. package/dist/commands/logout.d.ts +2 -0
  22. package/dist/commands/logout.d.ts.map +1 -0
  23. package/dist/commands/logout.js +18 -0
  24. package/dist/commands/pull.d.ts +7 -0
  25. package/dist/commands/pull.d.ts.map +1 -0
  26. package/dist/commands/pull.js +97 -0
  27. package/dist/compiler/errors.d.ts +20 -0
  28. package/dist/compiler/errors.d.ts.map +1 -0
  29. package/dist/compiler/errors.js +35 -0
  30. package/dist/compiler/evalStatic.d.ts +3 -0
  31. package/dist/compiler/evalStatic.d.ts.map +1 -0
  32. package/dist/compiler/evalStatic.js +57 -0
  33. package/dist/compiler/flatten.js +1 -0
  34. package/dist/compiler/helpers/duplicateIds.d.ts +9 -0
  35. package/dist/compiler/helpers/duplicateIds.d.ts.map +1 -0
  36. package/dist/compiler/helpers/duplicateIds.js +71 -0
  37. package/dist/compiler/index.d.ts +16 -0
  38. package/dist/compiler/index.d.ts.map +1 -0
  39. package/dist/compiler/index.js +934 -0
  40. package/dist/compiler/parsePage.d.ts +15 -0
  41. package/dist/compiler/parsePage.d.ts.map +1 -0
  42. package/dist/compiler/parsePage.js +562 -0
  43. package/dist/compiler/registry.d.ts +4 -0
  44. package/dist/compiler/registry.d.ts.map +1 -0
  45. package/dist/compiler/registry.js +44 -0
  46. package/dist/compiler/reverse.d.ts +17 -0
  47. package/dist/compiler/reverse.d.ts.map +1 -0
  48. package/dist/compiler/reverse.js +1632 -0
  49. package/dist/compiler/types.d.ts +21 -0
  50. package/dist/compiler/types.d.ts.map +1 -0
  51. package/dist/compiler/types.js +1 -0
  52. package/dist/components/index.d.ts +21 -0
  53. package/dist/components/index.d.ts.map +1 -0
  54. package/dist/components/index.js +21 -0
  55. package/dist/components/primitives/BaseComponent.d.ts +32 -0
  56. package/dist/components/primitives/BaseComponent.d.ts.map +1 -0
  57. package/dist/components/primitives/BaseComponent.js +26 -0
  58. package/dist/components/primitives/BookMeeting.d.ts +18 -0
  59. package/dist/components/primitives/BookMeeting.d.ts.map +1 -0
  60. package/dist/components/primitives/BookMeeting.js +5 -0
  61. package/dist/components/primitives/Chart.d.ts +41 -0
  62. package/dist/components/primitives/Chart.d.ts.map +1 -0
  63. package/dist/components/primitives/Chart.js +5 -0
  64. package/dist/components/primitives/Container.d.ts +8 -0
  65. package/dist/components/primitives/Container.d.ts.map +1 -0
  66. package/dist/components/primitives/Container.js +5 -0
  67. package/dist/components/primitives/CustomButton.d.ts +37 -0
  68. package/dist/components/primitives/CustomButton.d.ts.map +1 -0
  69. package/dist/components/primitives/CustomButton.js +10 -0
  70. package/dist/components/primitives/CustomHTML.d.ts +8 -0
  71. package/dist/components/primitives/CustomHTML.d.ts.map +1 -0
  72. package/dist/components/primitives/CustomHTML.js +5 -0
  73. package/dist/components/primitives/FileUpload.d.ts +18 -0
  74. package/dist/components/primitives/FileUpload.d.ts.map +1 -0
  75. package/dist/components/primitives/FileUpload.js +16 -0
  76. package/dist/components/primitives/InputBox.d.ts +34 -0
  77. package/dist/components/primitives/InputBox.d.ts.map +1 -0
  78. package/dist/components/primitives/InputBox.js +25 -0
  79. package/dist/components/primitives/Lottie.d.ts +11 -0
  80. package/dist/components/primitives/Lottie.d.ts.map +1 -0
  81. package/dist/components/primitives/Lottie.js +5 -0
  82. package/dist/components/primitives/MediaEmbed.d.ts +13 -0
  83. package/dist/components/primitives/MediaEmbed.d.ts.map +1 -0
  84. package/dist/components/primitives/MediaEmbed.js +6 -0
  85. package/dist/components/primitives/MediaImage.d.ts +8 -0
  86. package/dist/components/primitives/MediaImage.d.ts.map +1 -0
  87. package/dist/components/primitives/MediaImage.js +5 -0
  88. package/dist/components/primitives/OptionSelector.d.ts +35 -0
  89. package/dist/components/primitives/OptionSelector.d.ts.map +1 -0
  90. package/dist/components/primitives/OptionSelector.js +8 -0
  91. package/dist/components/primitives/PaypalCheckout.d.ts +25 -0
  92. package/dist/components/primitives/PaypalCheckout.d.ts.map +1 -0
  93. package/dist/components/primitives/PaypalCheckout.js +5 -0
  94. package/dist/components/primitives/PlainText.d.ts +6 -0
  95. package/dist/components/primitives/PlainText.d.ts.map +1 -0
  96. package/dist/components/primitives/PlainText.js +5 -0
  97. package/dist/components/primitives/ProgressBar.d.ts +15 -0
  98. package/dist/components/primitives/ProgressBar.d.ts.map +1 -0
  99. package/dist/components/primitives/ProgressBar.js +5 -0
  100. package/dist/components/primitives/RichText.d.ts +6 -0
  101. package/dist/components/primitives/RichText.d.ts.map +1 -0
  102. package/dist/components/primitives/RichText.js +5 -0
  103. package/dist/components/primitives/RichTextMarkdown.d.ts +6 -0
  104. package/dist/components/primitives/RichTextMarkdown.d.ts.map +1 -0
  105. package/dist/components/primitives/RichTextMarkdown.js +5 -0
  106. package/dist/components/primitives/Rive.d.ts +16 -0
  107. package/dist/components/primitives/Rive.d.ts.map +1 -0
  108. package/dist/components/primitives/Rive.js +8 -0
  109. package/dist/components/primitives/StripeCheckout.d.ts +52 -0
  110. package/dist/components/primitives/StripeCheckout.d.ts.map +1 -0
  111. package/dist/components/primitives/StripeCheckout.js +5 -0
  112. package/dist/components/primitives/StripeCheckout2.d.ts +30 -0
  113. package/dist/components/primitives/StripeCheckout2.d.ts.map +1 -0
  114. package/dist/components/primitives/StripeCheckout2.js +7 -0
  115. package/dist/proxy/injectApiInterceptor.d.ts +6 -0
  116. package/dist/proxy/injectApiInterceptor.d.ts.map +1 -0
  117. package/dist/proxy/injectApiInterceptor.js +66 -0
  118. package/dist/proxy/injectReload.d.ts +2 -0
  119. package/dist/proxy/injectReload.d.ts.map +1 -0
  120. package/dist/proxy/injectReload.js +14 -0
  121. package/dist/proxy/injectWorkbench.d.ts +4 -0
  122. package/dist/proxy/injectWorkbench.d.ts.map +1 -0
  123. package/dist/proxy/injectWorkbench.js +16 -0
  124. package/dist/proxy/server.d.ts +11 -0
  125. package/dist/proxy/server.d.ts.map +1 -0
  126. package/dist/proxy/server.js +246 -0
  127. package/dist/proxy/sse.d.ts +5 -0
  128. package/dist/proxy/sse.d.ts.map +1 -0
  129. package/dist/proxy/sse.js +17 -0
  130. package/dist/types-builder.d.ts +800 -0
  131. package/dist/types-builder.d.ts.map +1 -0
  132. package/dist/types-builder.js +20 -0
  133. package/dist/workbench/ActionsPanel.d.ts +6 -0
  134. package/dist/workbench/ActionsPanel.d.ts.map +1 -0
  135. package/dist/workbench/ActionsPanel.js +47 -0
  136. package/dist/workbench/AutofillPanel.d.ts +6 -0
  137. package/dist/workbench/AutofillPanel.d.ts.map +1 -0
  138. package/dist/workbench/AutofillPanel.js +543 -0
  139. package/dist/workbench/ComputedFieldsPanel.d.ts +6 -0
  140. package/dist/workbench/ComputedFieldsPanel.d.ts.map +1 -0
  141. package/dist/workbench/ComputedFieldsPanel.js +31 -0
  142. package/dist/workbench/ExperimentsPanel.d.ts +6 -0
  143. package/dist/workbench/ExperimentsPanel.d.ts.map +1 -0
  144. package/dist/workbench/ExperimentsPanel.js +182 -0
  145. package/dist/workbench/FieldEditorPanel.d.ts +9 -0
  146. package/dist/workbench/FieldEditorPanel.d.ts.map +1 -0
  147. package/dist/workbench/FieldEditorPanel.js +650 -0
  148. package/dist/workbench/InspectorPanel.d.ts +6 -0
  149. package/dist/workbench/InspectorPanel.d.ts.map +1 -0
  150. package/dist/workbench/InspectorPanel.js +341 -0
  151. package/dist/workbench/PageNavigator.d.ts +6 -0
  152. package/dist/workbench/PageNavigator.d.ts.map +1 -0
  153. package/dist/workbench/PageNavigator.js +123 -0
  154. package/dist/workbench/SchemaPanel.d.ts +6 -0
  155. package/dist/workbench/SchemaPanel.d.ts.map +1 -0
  156. package/dist/workbench/SchemaPanel.js +222 -0
  157. package/dist/workbench/UserDataPanel.d.ts +6 -0
  158. package/dist/workbench/UserDataPanel.d.ts.map +1 -0
  159. package/dist/workbench/UserDataPanel.js +350 -0
  160. package/dist/workbench/WorkbenchApp.d.ts +6 -0
  161. package/dist/workbench/WorkbenchApp.d.ts.map +1 -0
  162. package/dist/workbench/WorkbenchApp.js +193 -0
  163. package/dist/workbench/cloudflare-worker/README.md +31 -0
  164. package/dist/workbench/cloudflare-worker/public/workbench.css +1614 -0
  165. package/dist/workbench/cloudflare-worker/public/workbench.js +77 -0
  166. package/dist/workbench/cloudflare-worker/worker.js +40 -0
  167. package/dist/workbench/cloudflare-worker/wrangler.toml +10 -0
  168. package/dist/workbench/index.d.ts +9 -0
  169. package/dist/workbench/index.d.ts.map +1 -0
  170. package/dist/workbench/index.js +44 -0
  171. package/dist/workbench/workbench.css +1614 -0
  172. package/dist/workbench/workbench.js +77 -0
  173. package/package.json +79 -0
@@ -0,0 +1,650 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useRef, useState } from 'react';
3
+ const BUILT_IN_KEYS = [
4
+ 'entryId',
5
+ 'current_page_id',
6
+ 'current_page_key',
7
+ 'current_page_index',
8
+ 'highest_page_reached_id',
9
+ 'highest_page_reached_key',
10
+ 'highest_page_reached_index',
11
+ ];
12
+ // Component types that store data in user data
13
+ const INPUT_COMPONENT_TYPES = ['InputBox', 'OptionSelector', 'FileUpload'];
14
+ function getComponentTypeIcon(componentType) {
15
+ switch (componentType) {
16
+ case 'InputBox':
17
+ return '𝐓';
18
+ case 'OptionSelector':
19
+ return '☰';
20
+ case 'FileUpload':
21
+ return '📎';
22
+ case 'Multiple Types':
23
+ return '⊕';
24
+ default:
25
+ return '•';
26
+ }
27
+ }
28
+ function getInputTypeLabel(inputType) {
29
+ if (!inputType)
30
+ return null;
31
+ const labels = {
32
+ text: 'text',
33
+ email: 'email',
34
+ phone: 'phone',
35
+ number: 'number',
36
+ date: 'date',
37
+ time: 'time',
38
+ month: 'month',
39
+ password: 'password',
40
+ confirm: 'confirm',
41
+ range: 'range',
42
+ switch: 'switch',
43
+ checkbox: 'checkbox',
44
+ };
45
+ return labels[inputType] || inputType;
46
+ }
47
+ function getCategoryLabel(category) {
48
+ switch (category) {
49
+ case 'built-in':
50
+ return 'Built-in Keys';
51
+ case 'component':
52
+ return 'Component Fields';
53
+ case 'computed':
54
+ return 'Computed Fields';
55
+ case 'registered':
56
+ return 'Registered Keys';
57
+ case 'extra':
58
+ return 'Other Keys';
59
+ }
60
+ }
61
+ function getCategoryColor(category) {
62
+ switch (category) {
63
+ case 'built-in':
64
+ return 'text-purple-300';
65
+ case 'component':
66
+ return 'text-sky-300';
67
+ case 'computed':
68
+ return 'text-amber-300';
69
+ case 'registered':
70
+ return 'text-teal-300';
71
+ case 'extra':
72
+ return 'text-slate-300';
73
+ }
74
+ }
75
+ /** Restore cursor position after a parent update that would otherwise move it to the end */
76
+ function restoreCursorAfterUpdate(input, fn) {
77
+ const start = input.selectionStart ?? 0;
78
+ const end = input.selectionEnd ?? 0;
79
+ fn();
80
+ requestAnimationFrame(() => {
81
+ requestAnimationFrame(() => {
82
+ input.setSelectionRange(start, end);
83
+ });
84
+ });
85
+ }
86
+ function formatValue(value) {
87
+ if (value === null)
88
+ return 'null';
89
+ if (value === undefined)
90
+ return '';
91
+ if (typeof value === 'string')
92
+ return value;
93
+ if (typeof value === 'number' || typeof value === 'boolean')
94
+ return String(value);
95
+ if (Array.isArray(value))
96
+ return JSON.stringify(value, null, 2);
97
+ if (typeof value === 'object')
98
+ return JSON.stringify(value, null, 2);
99
+ return String(value);
100
+ }
101
+ function detectValueType(value) {
102
+ if (value === null || value === undefined)
103
+ return 'null';
104
+ if (typeof value === 'boolean')
105
+ return 'boolean';
106
+ if (typeof value === 'number')
107
+ return 'number';
108
+ if (typeof value === 'string')
109
+ return 'text';
110
+ return 'json';
111
+ }
112
+ function generateTestValue(field) {
113
+ const key = field.key.toLowerCase();
114
+ const label = (field.label || '').toLowerCase();
115
+ const context = `${key} ${label}`;
116
+ // Handle OptionSelector - return first option key
117
+ if (field.options && field.options.length > 0) {
118
+ const firstOption = field.options[0];
119
+ return firstOption.key ?? '';
120
+ }
121
+ // Handle InputBox types
122
+ switch (field.inputType) {
123
+ case 'email':
124
+ return 'test@example.com';
125
+ case 'phone':
126
+ return '+1 (555) 123-4567';
127
+ case 'number':
128
+ return '42';
129
+ case 'date':
130
+ return new Date().toISOString().split('T')[0];
131
+ case 'time':
132
+ return '12:00';
133
+ case 'month':
134
+ const now = new Date();
135
+ return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
136
+ case 'password':
137
+ case 'confirm':
138
+ return 'TestPassword123!';
139
+ case 'range':
140
+ return '50';
141
+ case 'switch':
142
+ case 'checkbox':
143
+ return 'true';
144
+ }
145
+ // Context-based text generation
146
+ if (context.includes('name') && context.includes('first'))
147
+ return 'John';
148
+ if (context.includes('name') && context.includes('last'))
149
+ return 'Doe';
150
+ if (context.includes('name') && context.includes('full'))
151
+ return 'John Doe';
152
+ if (context.includes('name'))
153
+ return 'John Doe';
154
+ if (context.includes('email'))
155
+ return 'test@example.com';
156
+ if (context.includes('phone'))
157
+ return '+1 (555) 123-4567';
158
+ if (context.includes('address') || context.includes('street'))
159
+ return '123 Main Street';
160
+ if (context.includes('city'))
161
+ return 'San Francisco';
162
+ if (context.includes('state'))
163
+ return 'California';
164
+ if (context.includes('zip') || context.includes('postal'))
165
+ return '94102';
166
+ if (context.includes('country'))
167
+ return 'United States';
168
+ if (context.includes('age'))
169
+ return '30';
170
+ if (context.includes('weight'))
171
+ return '150';
172
+ if (context.includes('height'))
173
+ return '5\'10"';
174
+ return 'Test value';
175
+ }
176
+ export function FieldEditorPanel({ embeddableId, rawJson, onUpdateUserData, keyFilter, }) {
177
+ const [fields, setFields] = useState([]);
178
+ const [allComponentFields, setAllComponentFields] = useState([]);
179
+ const [expandedCategories, setExpandedCategories] = useState(new Set(['built-in', 'component', 'computed', 'registered', 'extra']));
180
+ const [componentFilter, setComponentFilter] = useState('filled');
181
+ const [fieldTypes, setFieldTypes] = useState({});
182
+ const [currentPageId, setCurrentPageId] = useState('');
183
+ const savvy = window.Savvy;
184
+ const analyzeFields = useCallback(() => {
185
+ // Parse user data
186
+ let userData;
187
+ try {
188
+ userData = rawJson.trim() ? JSON.parse(rawJson) : {};
189
+ }
190
+ catch {
191
+ return;
192
+ }
193
+ // Get flow JSON for metadata
194
+ const flowJson = savvy?.getFlowJson?.(embeddableId);
195
+ // Store current page ID
196
+ setCurrentPageId(userData.current_page_id || '');
197
+ // Build component metadata map: key -> { types, inputType, options, label, pageIds, isMultiple }
198
+ const componentMeta = new Map();
199
+ const processComponent = (comp, pageId) => {
200
+ if (!comp.key)
201
+ return;
202
+ // Only track input-y components
203
+ if (!INPUT_COMPONENT_TYPES.includes(comp.type))
204
+ return;
205
+ const existing = componentMeta.get(comp.key) || { types: new Set(), pageIds: new Set() };
206
+ existing.types.add(comp.type);
207
+ if (pageId)
208
+ existing.pageIds.add(pageId);
209
+ if (comp.type === 'InputBox') {
210
+ const inputComp = comp;
211
+ existing.inputType = inputComp.input_type;
212
+ existing.label = comp.label;
213
+ }
214
+ else if (comp.type === 'OptionSelector') {
215
+ const optComp = comp;
216
+ existing.options = optComp.buttons?.map((b) => ({ key: b.key, text: b.text })) || [];
217
+ existing.label = comp.label;
218
+ existing.isMultiple = optComp.multiple;
219
+ }
220
+ else if (comp.label) {
221
+ existing.label = comp.label;
222
+ }
223
+ componentMeta.set(comp.key, existing);
224
+ // Process nested components
225
+ if (comp.components) {
226
+ comp.components.forEach((c) => processComponent(c, pageId));
227
+ }
228
+ };
229
+ // Process all pages and global components
230
+ flowJson?.pages?.forEach((page) => {
231
+ page.components?.forEach((c) => processComponent(c, page.id));
232
+ });
233
+ flowJson?.components?.forEach((c) => processComponent(c));
234
+ // Build computed fields set
235
+ const computedFieldKeys = new Set();
236
+ flowJson?.computedFields?.forEach((cf) => {
237
+ if (cf.key)
238
+ computedFieldKeys.add(cf.key);
239
+ });
240
+ // Build registered keys set
241
+ const registeredKeys = new Set();
242
+ if (flowJson?.registered_keys) {
243
+ const keys = flowJson.registered_keys.split(/[\n,]/).map((k) => k.trim());
244
+ keys.forEach((k) => {
245
+ if (k)
246
+ registeredKeys.add(k);
247
+ });
248
+ }
249
+ // Build pages list for dropdowns
250
+ const pagesList = [];
251
+ flowJson?.pages?.forEach((page, index) => {
252
+ pagesList.push({
253
+ id: page.id,
254
+ key: page.key,
255
+ index,
256
+ });
257
+ });
258
+ // Build ALL component fields
259
+ const allCompFields = [];
260
+ componentMeta.forEach((meta, key) => {
261
+ const value = userData[key];
262
+ // For multi-select, check if array has items
263
+ const hasValue = meta.isMultiple
264
+ ? Array.isArray(value) && value.length > 0
265
+ : value !== null && value !== undefined && value !== '';
266
+ allCompFields.push({
267
+ key,
268
+ value: hasValue ? value : undefined,
269
+ category: 'component',
270
+ componentType: meta.types.size > 1 ? 'Multiple Types' : Array.from(meta.types)[0],
271
+ inputType: meta.inputType,
272
+ options: meta.options,
273
+ label: meta.label,
274
+ hasValue,
275
+ pageIds: Array.from(meta.pageIds),
276
+ isMultiple: meta.isMultiple,
277
+ });
278
+ });
279
+ allCompFields.sort((a, b) => a.key.localeCompare(b.key));
280
+ setAllComponentFields(allCompFields);
281
+ // Categorize each key in userData (for non-component fields)
282
+ const fieldsList = [];
283
+ const processedKeys = new Set();
284
+ for (const [key, value] of Object.entries(userData)) {
285
+ let category = 'extra';
286
+ let componentType;
287
+ let inputType;
288
+ let options;
289
+ let label;
290
+ const hasValue = value !== null && value !== undefined && value !== '';
291
+ if (BUILT_IN_KEYS.includes(key)) {
292
+ category = 'built-in';
293
+ // Add page options for page-related keys (both current_page_* and highest_page_reached_*)
294
+ if (key === 'current_page_id' || key === 'highest_page_reached_id') {
295
+ options = pagesList.map((p) => ({ key: p.id, text: `${p.key} (${p.id})` }));
296
+ }
297
+ else if (key === 'current_page_key' || key === 'highest_page_reached_key') {
298
+ options = pagesList.map((p) => ({ key: p.key, text: p.key }));
299
+ }
300
+ else if (key === 'current_page_index' || key === 'highest_page_reached_index') {
301
+ options = pagesList.map((p) => ({ key: String(p.index), text: `${p.index} (${p.key})` }));
302
+ }
303
+ }
304
+ else if (componentMeta.has(key)) {
305
+ // Skip component fields - they're handled separately
306
+ processedKeys.add(key);
307
+ continue;
308
+ }
309
+ else if (computedFieldKeys.has(key)) {
310
+ category = 'computed';
311
+ }
312
+ else if (registeredKeys.has(key)) {
313
+ category = 'registered';
314
+ }
315
+ processedKeys.add(key);
316
+ fieldsList.push({
317
+ key,
318
+ value,
319
+ category,
320
+ componentType,
321
+ inputType,
322
+ options,
323
+ label,
324
+ hasValue,
325
+ });
326
+ }
327
+ // Sort fields by category and then by key
328
+ const categoryOrder = [
329
+ 'component',
330
+ 'computed',
331
+ 'registered',
332
+ 'built-in',
333
+ 'extra',
334
+ ];
335
+ fieldsList.sort((a, b) => {
336
+ const catDiff = categoryOrder.indexOf(a.category) - categoryOrder.indexOf(b.category);
337
+ if (catDiff !== 0)
338
+ return catDiff;
339
+ return a.key.localeCompare(b.key);
340
+ });
341
+ setFields(fieldsList);
342
+ }, [rawJson, embeddableId, savvy]);
343
+ useEffect(() => {
344
+ analyzeFields();
345
+ }, [analyzeFields]);
346
+ // Switch to "This Page" on initial load if no component fields are filled
347
+ const hasAutoSwitched = useRef(false);
348
+ useEffect(() => {
349
+ if (hasAutoSwitched.current)
350
+ return;
351
+ if (allComponentFields.length > 0) {
352
+ const hasFilledFields = allComponentFields.some((f) => f.hasValue);
353
+ if (!hasFilledFields) {
354
+ setComponentFilter('thisPage');
355
+ }
356
+ hasAutoSwitched.current = true;
357
+ }
358
+ }, [allComponentFields]);
359
+ const toggleCategory = (category) => {
360
+ setExpandedCategories((prev) => {
361
+ const next = new Set(prev);
362
+ if (next.has(category)) {
363
+ next.delete(category);
364
+ }
365
+ else {
366
+ next.add(category);
367
+ }
368
+ return next;
369
+ });
370
+ };
371
+ const handleClear = (field) => {
372
+ // For multi-select fields, clear to empty array instead of null
373
+ if (field.isMultiple) {
374
+ onUpdateUserData(field.key, []);
375
+ }
376
+ else {
377
+ onUpdateUserData(field.key, null);
378
+ }
379
+ };
380
+ const handleFill = (field) => {
381
+ const value = generateTestValue(field);
382
+ // Convert to appropriate type
383
+ let typedValue = value;
384
+ // For multi-select, wrap in array
385
+ if (field.isMultiple && field.options && field.options.length > 0) {
386
+ typedValue = [value];
387
+ }
388
+ else if (field.inputType === 'number' || field.inputType === 'range') {
389
+ typedValue = Number(value);
390
+ }
391
+ else if (field.inputType === 'switch' || field.inputType === 'checkbox') {
392
+ typedValue = value === 'true';
393
+ }
394
+ else if (field.key.includes('page_index') || field.key.includes('_index')) {
395
+ typedValue = Number(value);
396
+ }
397
+ onUpdateUserData(field.key, typedValue);
398
+ };
399
+ const handleValueChange = (field, newValue) => {
400
+ let typedValue = newValue;
401
+ // Try to parse as JSON for complex values
402
+ if (newValue.startsWith('{') || newValue.startsWith('[')) {
403
+ try {
404
+ typedValue = JSON.parse(newValue);
405
+ }
406
+ catch {
407
+ typedValue = newValue;
408
+ }
409
+ }
410
+ else if (newValue === 'null') {
411
+ typedValue = null;
412
+ }
413
+ else if (newValue === 'true') {
414
+ typedValue = true;
415
+ }
416
+ else if (newValue === 'false') {
417
+ typedValue = false;
418
+ }
419
+ else if (newValue === '' || newValue === 'undefined') {
420
+ typedValue = null;
421
+ }
422
+ else if (field.inputType === 'number' ||
423
+ field.inputType === 'range' ||
424
+ field.key.includes('_index')) {
425
+ const num = Number(newValue);
426
+ if (!isNaN(num)) {
427
+ typedValue = num;
428
+ }
429
+ }
430
+ onUpdateUserData(field.key, typedValue);
431
+ };
432
+ const handleTypedValueChange = (key, valueType, newValue) => {
433
+ let typedValue;
434
+ switch (valueType) {
435
+ case 'null':
436
+ typedValue = null;
437
+ break;
438
+ case 'boolean':
439
+ typedValue = newValue === 'true';
440
+ break;
441
+ case 'number':
442
+ typedValue = newValue === '' ? null : Number(newValue);
443
+ if (isNaN(typedValue))
444
+ typedValue = null;
445
+ break;
446
+ case 'json':
447
+ try {
448
+ typedValue = JSON.parse(newValue);
449
+ }
450
+ catch {
451
+ typedValue = newValue;
452
+ }
453
+ break;
454
+ case 'text':
455
+ default:
456
+ typedValue = newValue === '' ? null : newValue;
457
+ break;
458
+ }
459
+ onUpdateUserData(key, typedValue);
460
+ };
461
+ const handleTypeChange = (key, newType, currentValue) => {
462
+ setFieldTypes((prev) => ({ ...prev, [key]: newType }));
463
+ // Convert value to new type
464
+ let convertedValue;
465
+ switch (newType) {
466
+ case 'null':
467
+ convertedValue = null;
468
+ break;
469
+ case 'boolean':
470
+ convertedValue = Boolean(currentValue);
471
+ break;
472
+ case 'number':
473
+ const num = Number(currentValue);
474
+ convertedValue = isNaN(num) ? 0 : num;
475
+ break;
476
+ case 'json':
477
+ if (typeof currentValue === 'object') {
478
+ convertedValue = currentValue;
479
+ }
480
+ else {
481
+ try {
482
+ convertedValue = JSON.parse(String(currentValue));
483
+ }
484
+ catch {
485
+ convertedValue = {};
486
+ }
487
+ }
488
+ break;
489
+ case 'text':
490
+ default:
491
+ convertedValue = String(currentValue ?? '');
492
+ break;
493
+ }
494
+ onUpdateUserData(key, convertedValue);
495
+ };
496
+ // Get the display component fields based on filter mode
497
+ const displayComponentFields = (() => {
498
+ switch (componentFilter) {
499
+ case 'all':
500
+ return allComponentFields;
501
+ case 'thisPage':
502
+ return allComponentFields.filter((f) => f.pageIds?.includes(currentPageId));
503
+ case 'filled':
504
+ default:
505
+ return allComponentFields.filter((f) => f.hasValue);
506
+ }
507
+ })();
508
+ // Filter fields
509
+ const filteredFields = keyFilter.trim()
510
+ ? fields.filter((f) => f.key.toLowerCase().includes(keyFilter.toLowerCase()))
511
+ : fields;
512
+ const filteredComponentFields = keyFilter.trim()
513
+ ? displayComponentFields.filter((f) => f.key.toLowerCase().includes(keyFilter.toLowerCase()))
514
+ : displayComponentFields;
515
+ // Group fields by category (excluding component fields which are handled separately)
516
+ const groupedFields = {
517
+ 'built-in': [],
518
+ component: [], // Will be replaced by filteredComponentFields
519
+ computed: [],
520
+ registered: [],
521
+ extra: [],
522
+ };
523
+ filteredFields.forEach((field) => {
524
+ groupedFields[field.category].push(field);
525
+ });
526
+ // Render built-in field (no clear button)
527
+ const renderBuiltInField = (field) => {
528
+ const hasOptions = field.options && field.options.length > 0;
529
+ const isFilled = field.value !== null && field.value !== undefined && field.value !== '';
530
+ return (_jsxs("div", { className: `flex items-center gap-2 rounded-lg px-2.5 py-2 ${isFilled
531
+ ? 'bg-emerald-500/10 ring-1 ring-inset ring-emerald-500/20'
532
+ : 'bg-white/5 ring-1 ring-inset ring-white/10'}`, children: [_jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [_jsx("span", { className: "flex h-6 w-6 shrink-0 items-center justify-center rounded bg-slate-800 text-xs", children: "\u2022" }), _jsx("div", { className: "min-w-0 flex-1", children: _jsx("div", { className: "truncate text-xs font-medium text-slate-100", title: field.key, children: field.key }) })] }), _jsx("div", { className: "flex shrink-0 items-center gap-2", children: hasOptions ? (_jsxs("select", { value: formatValue(field.value), onChange: (e) => handleValueChange(field, e.target.value), className: "max-w-[200px] truncate rounded-lg bg-slate-800 px-2 py-1.5 text-[11px] text-slate-100 ring-1 ring-inset ring-slate-600 focus:outline-none focus:ring-2 focus:ring-sky-500/60", children: [_jsx("option", { value: "", children: "-- Select --" }), field.options.map((opt, idx) => (_jsx("option", { value: opt.key ?? '', children: opt.text || opt.key || '(empty)' }, opt.key ?? idx)))] })) : (_jsx("input", { type: "text", value: formatValue(field.value), onChange: (e) => {
533
+ const input = e.target;
534
+ const value = input.value;
535
+ restoreCursorAfterUpdate(input, () => handleValueChange(field, value));
536
+ }, className: "w-40 rounded-lg bg-slate-800 px-2 py-1.5 text-[11px] font-mono text-slate-100 ring-1 ring-inset ring-slate-600 placeholder:text-slate-500 focus:outline-none focus:ring-2 focus:ring-sky-500/60", placeholder: "null" })) })] }, field.key));
537
+ };
538
+ // Handle adding a value to a multi-select field
539
+ const handleMultiSelectAdd = (field, keyToAdd) => {
540
+ if (!keyToAdd)
541
+ return;
542
+ const currentValues = Array.isArray(field.value) ? field.value : [];
543
+ if (!currentValues.includes(keyToAdd)) {
544
+ onUpdateUserData(field.key, [...currentValues, keyToAdd]);
545
+ }
546
+ };
547
+ // Handle removing a value from a multi-select field
548
+ const handleMultiSelectRemove = (field, keyToRemove) => {
549
+ const currentValues = Array.isArray(field.value) ? field.value : [];
550
+ onUpdateUserData(field.key, currentValues.filter((v) => v !== keyToRemove));
551
+ };
552
+ // Get display text for an option key
553
+ const getOptionText = (field, optionKey) => {
554
+ const option = field.options?.find((opt) => opt.key === optionKey);
555
+ return option?.text || option?.key || optionKey || '(empty)';
556
+ };
557
+ // Render component field (with clear/fill buttons)
558
+ const renderComponentField = (field) => {
559
+ const hasOptions = field.options && field.options.length > 0;
560
+ const isFilled = field.hasValue;
561
+ const isMultiSelect = field.isMultiple && hasOptions;
562
+ // For multi-select, get the selected values as array
563
+ const selectedValues = isMultiSelect
564
+ ? Array.isArray(field.value)
565
+ ? field.value
566
+ : []
567
+ : [];
568
+ // Get available options (not yet selected) for multi-select dropdown
569
+ const availableOptions = isMultiSelect
570
+ ? field.options.filter((opt) => !selectedValues.includes(opt.key ?? ''))
571
+ : field.options;
572
+ return (_jsxs("div", { className: `flex flex-col gap-2 rounded-lg px-2.5 py-2 ${isFilled
573
+ ? 'bg-emerald-500/10 ring-1 ring-inset ring-emerald-500/20'
574
+ : 'bg-white/5 ring-1 ring-inset ring-white/10'}`, children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [_jsx("span", { className: "flex h-6 w-6 shrink-0 items-center justify-center rounded bg-slate-800 text-xs", title: field.componentType || field.category, children: getComponentTypeIcon(field.componentType) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: `truncate text-xs font-medium text-slate-100 ${!field.label ? 'font-mono' : ''}`, title: field.key, children: field.label || field.key }), _jsxs("div", { className: "flex items-center gap-2 text-[10px] text-slate-500", children: [field.label && field.label !== field.key && (_jsxs(_Fragment, { children: [_jsx("span", { className: "font-mono text-slate-300", children: field.key }), _jsx("span", { children: "\u2022" })] })), field.componentType && (_jsxs(_Fragment, { children: [_jsx("span", { children: field.componentType }), field.isMultiple && (_jsxs(_Fragment, { children: [_jsx("span", { children: "\u2022" }), _jsx("span", { children: "Multi-select" })] })), field.inputType && (_jsxs(_Fragment, { children: [_jsx("span", { children: "\u2022" }), _jsx("span", { className: "font-mono", children: getInputTypeLabel(field.inputType) })] }))] })), field.options && field.options.length > 0 && (_jsxs(_Fragment, { children: [_jsx("span", { children: "\u2022" }), _jsxs("span", { children: [field.options.length, " options"] })] }))] })] })] }), !isMultiSelect && (_jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [hasOptions ? (_jsxs("select", { value: formatValue(field.value), onChange: (e) => handleValueChange(field, e.target.value), className: "max-w-[180px] truncate rounded-lg bg-slate-800 px-2 py-1.5 text-[11px] text-slate-100 ring-1 ring-inset ring-slate-600 focus:outline-none focus:ring-2 focus:ring-sky-500/60", children: [_jsx("option", { value: "", children: "-- Select --" }), field.options.map((opt, idx) => (_jsx("option", { value: opt.key ?? '', children: opt.text || opt.key || '(empty)' }, opt.key ?? idx)))] })) : (_jsx("input", { type: "text", value: formatValue(field.value), onChange: (e) => {
575
+ const input = e.target;
576
+ const value = input.value;
577
+ restoreCursorAfterUpdate(input, () => handleValueChange(field, value));
578
+ }, className: "w-32 rounded-lg bg-slate-800 px-2 py-1.5 text-[11px] font-mono text-slate-100 ring-1 ring-inset ring-slate-600 placeholder:text-slate-500 focus:outline-none focus:ring-2 focus:ring-sky-500/60", placeholder: "null" })), isFilled ? (_jsx("button", { type: "button", onClick: () => handleClear(field), className: "cursor-pointer rounded-lg bg-slate-700 px-2 py-1 text-[10px] font-semibold text-rose-300 ring-1 ring-inset ring-slate-600 hover:bg-slate-600 hover:text-rose-200", children: "Clear" })) : (_jsx("button", { type: "button", onClick: () => handleFill(field), className: "cursor-pointer rounded-lg bg-slate-700 px-2 py-1 text-[10px] font-semibold text-teal-300 ring-1 ring-inset ring-slate-600 hover:bg-slate-600 hover:text-teal-200", children: "Fill" }))] })), isMultiSelect && (_jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [_jsxs("select", { value: "", onChange: (e) => handleMultiSelectAdd(field, e.target.value), className: "max-w-[180px] truncate rounded-lg bg-slate-800 px-2 py-1.5 text-[11px] text-slate-100 ring-1 ring-inset ring-slate-600 focus:outline-none focus:ring-2 focus:ring-sky-500/60", children: [_jsx("option", { value: "", children: "+ Add..." }), availableOptions.map((opt, idx) => (_jsx("option", { value: opt.key ?? '', children: opt.text || opt.key || '(empty)' }, opt.key ?? idx)))] }), isFilled && (_jsx("button", { type: "button", onClick: () => handleClear(field), className: "cursor-pointer rounded-lg bg-slate-700 px-2 py-1 text-[10px] font-semibold text-rose-300 ring-1 ring-inset ring-slate-600 hover:bg-slate-600 hover:text-rose-200", children: "Clear All" }))] }))] }), isMultiSelect && selectedValues.length > 0 && (_jsx("div", { className: "flex flex-wrap gap-1.5 pl-8", children: selectedValues.map((val) => (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-md bg-emerald-500/20 px-2 py-0.5 text-[11px] font-medium text-emerald-200 ring-1 ring-inset ring-emerald-500/30", children: [getOptionText(field, val), _jsx("button", { type: "button", onClick: () => handleMultiSelectRemove(field, val), className: "ml-0.5 cursor-pointer rounded hover:bg-sky-500/30 hover:text-sky-100", title: "Remove", children: _jsx("svg", { className: "h-3 w-3", viewBox: "0 0 12 12", fill: "none", stroke: "currentColor", strokeWidth: "2", children: _jsx("path", { d: "M3 3l6 6M9 3l-6 6" }) }) })] }, val))) }))] }, field.key));
579
+ };
580
+ // Render typed field (for computed, registered, extra)
581
+ const renderTypedField = (field) => {
582
+ const currentType = fieldTypes[field.key] ?? detectValueType(field.value);
583
+ const isFilled = field.hasValue;
584
+ return (_jsxs("div", { className: `flex items-center gap-2 rounded-lg px-2.5 py-2 ${isFilled
585
+ ? 'bg-emerald-500/10 ring-1 ring-inset ring-emerald-500/20'
586
+ : 'bg-white/5 ring-1 ring-inset ring-white/10'}`, children: [_jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [_jsx("span", { className: "flex h-6 w-6 shrink-0 items-center justify-center rounded bg-slate-800 text-xs", children: "\u2022" }), _jsx("div", { className: "min-w-0 flex-1", children: _jsx("div", { className: "truncate text-xs font-medium text-slate-100", title: field.key, children: field.key }) })] }), _jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [_jsxs("select", { value: currentType, onChange: (e) => handleTypeChange(field.key, e.target.value, field.value), className: "w-20 rounded-lg bg-slate-800 px-2 py-1.5 text-[11px] text-slate-100 ring-1 ring-inset ring-slate-600 focus:outline-none focus:ring-2 focus:ring-sky-500/60", children: [_jsx("option", { value: "null", children: "null" }), _jsx("option", { value: "boolean", children: "boolean" }), _jsx("option", { value: "text", children: "text" }), _jsx("option", { value: "number", children: "number" }), _jsx("option", { value: "json", children: "JSON" })] }), currentType === 'null' ? (_jsx("span", { className: "w-24 rounded-lg bg-slate-800/50 px-2 py-1.5 text-center text-[11px] font-mono text-slate-500 ring-1 ring-inset ring-slate-700", children: "null" })) : currentType === 'boolean' ? (_jsxs("select", { value: String(field.value ?? false), onChange: (e) => handleTypedValueChange(field.key, 'boolean', e.target.value), className: "w-24 rounded-lg bg-slate-800 px-2 py-1.5 text-[11px] text-slate-100 ring-1 ring-inset ring-slate-600 focus:outline-none focus:ring-2 focus:ring-sky-500/60", children: [_jsx("option", { value: "true", children: "true" }), _jsx("option", { value: "false", children: "false" })] })) : currentType === 'number' ? (_jsx("input", { type: "number", value: field.value === null || field.value === undefined ? '' : String(field.value), onChange: (e) => {
587
+ const input = e.target;
588
+ const value = input.value;
589
+ restoreCursorAfterUpdate(input, () => handleTypedValueChange(field.key, 'number', value));
590
+ }, className: "w-24 rounded-lg bg-slate-800 px-2 py-1.5 text-[11px] font-mono text-slate-100 ring-1 ring-inset ring-slate-600 placeholder:text-slate-500 focus:outline-none focus:ring-2 focus:ring-sky-500/60", placeholder: "0" })) : currentType === 'json' ? (_jsx("textarea", { value: formatValue(field.value), onChange: (e) => {
591
+ const input = e.target;
592
+ const value = input.value;
593
+ restoreCursorAfterUpdate(input, () => handleTypedValueChange(field.key, 'json', value));
594
+ }, className: "h-8 w-32 resize-none rounded-lg bg-slate-800 px-2 py-1 text-[10px] font-mono text-slate-100 ring-1 ring-inset ring-slate-600 placeholder:text-slate-500 focus:outline-none focus:ring-2 focus:ring-sky-500/60", placeholder: "{}" })) : (_jsx("input", { type: "text", value: field.value === null || field.value === undefined ? '' : String(field.value), onChange: (e) => {
595
+ const input = e.target;
596
+ const value = input.value;
597
+ restoreCursorAfterUpdate(input, () => handleTypedValueChange(field.key, 'text', value));
598
+ }, className: "w-32 rounded-lg bg-slate-800 px-2 py-1.5 text-[11px] font-mono text-slate-100 ring-1 ring-inset ring-slate-600 placeholder:text-slate-500 focus:outline-none focus:ring-2 focus:ring-sky-500/60", placeholder: "" })), isFilled && (_jsx("button", { type: "button", onClick: () => handleClear(field), className: "cursor-pointer rounded-lg bg-slate-700 px-2 py-1 text-[10px] font-semibold text-rose-300 ring-1 ring-inset ring-slate-600 hover:bg-slate-600 hover:text-rose-200", children: "Clear" }))] })] }, field.key));
599
+ };
600
+ const categories = ['component', 'computed', 'registered', 'built-in', 'extra'];
601
+ return (_jsx("div", { className: "flex min-h-0 flex-1 flex-col", children: _jsx("div", { className: "min-h-0 flex-1 overflow-auto rounded-xl bg-slate-950/60 p-2 ring-1 ring-inset ring-white/10", children: _jsx("div", { className: "space-y-3", children: categories.map((category) => {
602
+ // Special handling for component fields
603
+ if (category === 'component') {
604
+ if (allComponentFields.length === 0)
605
+ return null;
606
+ const isExpanded = expandedCategories.has(category);
607
+ const filledCount = allComponentFields.filter((f) => f.hasValue).length;
608
+ const thisPageFields = allComponentFields.filter((f) => f.pageIds?.includes(currentPageId));
609
+ const thisPageFilledCount = thisPageFields.filter((f) => f.hasValue).length;
610
+ return (_jsxs("div", { children: [_jsxs("div", { className: "flex w-full items-center justify-between rounded-lg bg-slate-800/50 px-2.5 py-2 hover:bg-slate-800", children: [_jsxs("button", { type: "button", onClick: () => toggleCategory(category), className: "flex cursor-pointer items-center gap-2", children: [_jsx("span", { className: "text-[11px] text-slate-400", children: isExpanded ? '▼' : '▶' }), _jsx("span", { className: `text-[11px] font-semibold uppercase tracking-wider ${getCategoryColor(category)}`, children: getCategoryLabel(category) })] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("div", { className: "flex items-center rounded-lg bg-slate-900/50 p-0.5 ring-1 ring-inset ring-slate-700", children: [_jsx("button", { type: "button", onClick: (e) => {
611
+ e.stopPropagation();
612
+ setComponentFilter('filled');
613
+ }, className: `cursor-pointer rounded-md px-2 py-1 text-[10px] font-medium transition-colors ${componentFilter === 'filled'
614
+ ? 'bg-slate-700 text-slate-100'
615
+ : 'text-slate-400 hover:text-slate-300'}`, children: "Filled" }), _jsx("button", { type: "button", onClick: (e) => {
616
+ e.stopPropagation();
617
+ setComponentFilter('thisPage');
618
+ }, className: `cursor-pointer rounded-md px-2 py-1 text-[10px] font-medium transition-colors ${componentFilter === 'thisPage'
619
+ ? 'bg-slate-700 text-slate-100'
620
+ : 'text-slate-400 hover:text-slate-300'}`, children: "This Page" }), _jsx("button", { type: "button", onClick: (e) => {
621
+ e.stopPropagation();
622
+ setComponentFilter('all');
623
+ }, className: `cursor-pointer rounded-md px-2 py-1 text-[10px] font-medium transition-colors ${componentFilter === 'all'
624
+ ? 'bg-slate-700 text-slate-100'
625
+ : 'text-slate-400 hover:text-slate-300'}`, children: "All" })] }), _jsxs("div", { className: "flex items-center justify-end gap-1.5 min-w-18", children: [_jsx("span", { className: "inline-flex items-center gap-1 rounded-full bg-emerald-500/20 px-2 py-0.5 text-[10px] font-medium text-emerald-300 ring-1 ring-inset ring-emerald-500/30", children: componentFilter === 'thisPage' ? thisPageFilledCount : filledCount }), _jsx("span", { className: "text-[10px] text-slate-500", children: "/" }), _jsx("span", { className: "inline-flex items-center gap-1 rounded-full bg-slate-700/50 px-2 py-0.5 text-[10px] font-medium text-slate-300 ring-1 ring-inset ring-slate-600/50", children: componentFilter === 'thisPage'
626
+ ? thisPageFields.length
627
+ : allComponentFields.length })] })] })] }), isExpanded && (_jsx("div", { className: "mt-1 space-y-1", children: filteredComponentFields.length === 0 ? (_jsx("div", { className: "px-2 py-2 text-[11px] text-slate-500", children: componentFilter === 'filled'
628
+ ? 'No components with values'
629
+ : componentFilter === 'thisPage'
630
+ ? 'No components on this page'
631
+ : 'No components match the filter' })) : (filteredComponentFields.map(renderComponentField)) }))] }, category));
632
+ }
633
+ // Built-in fields
634
+ if (category === 'built-in') {
635
+ const categoryFields = groupedFields[category];
636
+ if (categoryFields.length === 0)
637
+ return null;
638
+ const isExpanded = expandedCategories.has(category);
639
+ return (_jsxs("div", { children: [_jsx("button", { type: "button", onClick: () => toggleCategory(category), className: "flex w-full cursor-pointer items-center justify-between rounded-lg bg-slate-800/50 px-2.5 py-2 hover:bg-slate-800", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-[11px] text-slate-400", children: isExpanded ? '▼' : '▶' }), _jsx("span", { className: `text-[11px] font-semibold uppercase tracking-wider ${getCategoryColor(category)}`, children: getCategoryLabel(category) })] }) }), isExpanded && (_jsx("div", { className: "mt-1 space-y-1", children: categoryFields.map(renderBuiltInField) }))] }, category));
640
+ }
641
+ // Computed, registered, and extra fields with type dropdowns
642
+ const categoryFields = groupedFields[category];
643
+ if (categoryFields.length === 0)
644
+ return null;
645
+ const isExpanded = expandedCategories.has(category);
646
+ const filledCount = categoryFields.filter((f) => f.hasValue).length;
647
+ const showCounts = category !== 'extra';
648
+ return (_jsxs("div", { children: [_jsxs("button", { type: "button", onClick: () => toggleCategory(category), className: "flex w-full cursor-pointer items-center justify-between rounded-lg bg-slate-800/50 px-2.5 py-2 hover:bg-slate-800", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-[11px] text-slate-400", children: isExpanded ? '▼' : '▶' }), _jsx("span", { className: `text-[11px] font-semibold uppercase tracking-wider ${getCategoryColor(category)}`, children: getCategoryLabel(category) })] }), showCounts && (_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: "inline-flex items-center gap-1 rounded-full bg-emerald-500/20 px-2 py-0.5 text-[10px] font-medium text-emerald-300 ring-1 ring-inset ring-emerald-500/30", children: filledCount }), _jsx("span", { className: "text-[10px] text-slate-500", children: "/" }), _jsx("span", { className: "inline-flex items-center gap-1 rounded-full bg-slate-700/50 px-2 py-0.5 text-[10px] font-medium text-slate-300 ring-1 ring-inset ring-slate-600/50", children: categoryFields.length })] }))] }), isExpanded && (_jsx("div", { className: "mt-1 space-y-1", children: categoryFields.map(renderTypedField) }))] }, category));
649
+ }) }) }) }));
650
+ }