@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.
- package/README.md +116 -0
- package/bin/embeddables.mjs +2 -0
- package/dist/auth/index.d.ts +43 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +100 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +75 -0
- package/dist/commands/build-workbench.d.ts +5 -0
- package/dist/commands/build-workbench.d.ts.map +1 -0
- package/dist/commands/build-workbench.js +122 -0
- package/dist/commands/build.d.ts +7 -0
- package/dist/commands/build.d.ts.map +1 -0
- package/dist/commands/build.js +22 -0
- package/dist/commands/dev.d.ts +11 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +153 -0
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +112 -0
- package/dist/commands/logout.d.ts +2 -0
- package/dist/commands/logout.d.ts.map +1 -0
- package/dist/commands/logout.js +18 -0
- package/dist/commands/pull.d.ts +7 -0
- package/dist/commands/pull.d.ts.map +1 -0
- package/dist/commands/pull.js +97 -0
- package/dist/compiler/errors.d.ts +20 -0
- package/dist/compiler/errors.d.ts.map +1 -0
- package/dist/compiler/errors.js +35 -0
- package/dist/compiler/evalStatic.d.ts +3 -0
- package/dist/compiler/evalStatic.d.ts.map +1 -0
- package/dist/compiler/evalStatic.js +57 -0
- package/dist/compiler/flatten.js +1 -0
- package/dist/compiler/helpers/duplicateIds.d.ts +9 -0
- package/dist/compiler/helpers/duplicateIds.d.ts.map +1 -0
- package/dist/compiler/helpers/duplicateIds.js +71 -0
- package/dist/compiler/index.d.ts +16 -0
- package/dist/compiler/index.d.ts.map +1 -0
- package/dist/compiler/index.js +934 -0
- package/dist/compiler/parsePage.d.ts +15 -0
- package/dist/compiler/parsePage.d.ts.map +1 -0
- package/dist/compiler/parsePage.js +562 -0
- package/dist/compiler/registry.d.ts +4 -0
- package/dist/compiler/registry.d.ts.map +1 -0
- package/dist/compiler/registry.js +44 -0
- package/dist/compiler/reverse.d.ts +17 -0
- package/dist/compiler/reverse.d.ts.map +1 -0
- package/dist/compiler/reverse.js +1632 -0
- package/dist/compiler/types.d.ts +21 -0
- package/dist/compiler/types.d.ts.map +1 -0
- package/dist/compiler/types.js +1 -0
- package/dist/components/index.d.ts +21 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +21 -0
- package/dist/components/primitives/BaseComponent.d.ts +32 -0
- package/dist/components/primitives/BaseComponent.d.ts.map +1 -0
- package/dist/components/primitives/BaseComponent.js +26 -0
- package/dist/components/primitives/BookMeeting.d.ts +18 -0
- package/dist/components/primitives/BookMeeting.d.ts.map +1 -0
- package/dist/components/primitives/BookMeeting.js +5 -0
- package/dist/components/primitives/Chart.d.ts +41 -0
- package/dist/components/primitives/Chart.d.ts.map +1 -0
- package/dist/components/primitives/Chart.js +5 -0
- package/dist/components/primitives/Container.d.ts +8 -0
- package/dist/components/primitives/Container.d.ts.map +1 -0
- package/dist/components/primitives/Container.js +5 -0
- package/dist/components/primitives/CustomButton.d.ts +37 -0
- package/dist/components/primitives/CustomButton.d.ts.map +1 -0
- package/dist/components/primitives/CustomButton.js +10 -0
- package/dist/components/primitives/CustomHTML.d.ts +8 -0
- package/dist/components/primitives/CustomHTML.d.ts.map +1 -0
- package/dist/components/primitives/CustomHTML.js +5 -0
- package/dist/components/primitives/FileUpload.d.ts +18 -0
- package/dist/components/primitives/FileUpload.d.ts.map +1 -0
- package/dist/components/primitives/FileUpload.js +16 -0
- package/dist/components/primitives/InputBox.d.ts +34 -0
- package/dist/components/primitives/InputBox.d.ts.map +1 -0
- package/dist/components/primitives/InputBox.js +25 -0
- package/dist/components/primitives/Lottie.d.ts +11 -0
- package/dist/components/primitives/Lottie.d.ts.map +1 -0
- package/dist/components/primitives/Lottie.js +5 -0
- package/dist/components/primitives/MediaEmbed.d.ts +13 -0
- package/dist/components/primitives/MediaEmbed.d.ts.map +1 -0
- package/dist/components/primitives/MediaEmbed.js +6 -0
- package/dist/components/primitives/MediaImage.d.ts +8 -0
- package/dist/components/primitives/MediaImage.d.ts.map +1 -0
- package/dist/components/primitives/MediaImage.js +5 -0
- package/dist/components/primitives/OptionSelector.d.ts +35 -0
- package/dist/components/primitives/OptionSelector.d.ts.map +1 -0
- package/dist/components/primitives/OptionSelector.js +8 -0
- package/dist/components/primitives/PaypalCheckout.d.ts +25 -0
- package/dist/components/primitives/PaypalCheckout.d.ts.map +1 -0
- package/dist/components/primitives/PaypalCheckout.js +5 -0
- package/dist/components/primitives/PlainText.d.ts +6 -0
- package/dist/components/primitives/PlainText.d.ts.map +1 -0
- package/dist/components/primitives/PlainText.js +5 -0
- package/dist/components/primitives/ProgressBar.d.ts +15 -0
- package/dist/components/primitives/ProgressBar.d.ts.map +1 -0
- package/dist/components/primitives/ProgressBar.js +5 -0
- package/dist/components/primitives/RichText.d.ts +6 -0
- package/dist/components/primitives/RichText.d.ts.map +1 -0
- package/dist/components/primitives/RichText.js +5 -0
- package/dist/components/primitives/RichTextMarkdown.d.ts +6 -0
- package/dist/components/primitives/RichTextMarkdown.d.ts.map +1 -0
- package/dist/components/primitives/RichTextMarkdown.js +5 -0
- package/dist/components/primitives/Rive.d.ts +16 -0
- package/dist/components/primitives/Rive.d.ts.map +1 -0
- package/dist/components/primitives/Rive.js +8 -0
- package/dist/components/primitives/StripeCheckout.d.ts +52 -0
- package/dist/components/primitives/StripeCheckout.d.ts.map +1 -0
- package/dist/components/primitives/StripeCheckout.js +5 -0
- package/dist/components/primitives/StripeCheckout2.d.ts +30 -0
- package/dist/components/primitives/StripeCheckout2.d.ts.map +1 -0
- package/dist/components/primitives/StripeCheckout2.js +7 -0
- package/dist/proxy/injectApiInterceptor.d.ts +6 -0
- package/dist/proxy/injectApiInterceptor.d.ts.map +1 -0
- package/dist/proxy/injectApiInterceptor.js +66 -0
- package/dist/proxy/injectReload.d.ts +2 -0
- package/dist/proxy/injectReload.d.ts.map +1 -0
- package/dist/proxy/injectReload.js +14 -0
- package/dist/proxy/injectWorkbench.d.ts +4 -0
- package/dist/proxy/injectWorkbench.d.ts.map +1 -0
- package/dist/proxy/injectWorkbench.js +16 -0
- package/dist/proxy/server.d.ts +11 -0
- package/dist/proxy/server.d.ts.map +1 -0
- package/dist/proxy/server.js +246 -0
- package/dist/proxy/sse.d.ts +5 -0
- package/dist/proxy/sse.d.ts.map +1 -0
- package/dist/proxy/sse.js +17 -0
- package/dist/types-builder.d.ts +800 -0
- package/dist/types-builder.d.ts.map +1 -0
- package/dist/types-builder.js +20 -0
- package/dist/workbench/ActionsPanel.d.ts +6 -0
- package/dist/workbench/ActionsPanel.d.ts.map +1 -0
- package/dist/workbench/ActionsPanel.js +47 -0
- package/dist/workbench/AutofillPanel.d.ts +6 -0
- package/dist/workbench/AutofillPanel.d.ts.map +1 -0
- package/dist/workbench/AutofillPanel.js +543 -0
- package/dist/workbench/ComputedFieldsPanel.d.ts +6 -0
- package/dist/workbench/ComputedFieldsPanel.d.ts.map +1 -0
- package/dist/workbench/ComputedFieldsPanel.js +31 -0
- package/dist/workbench/ExperimentsPanel.d.ts +6 -0
- package/dist/workbench/ExperimentsPanel.d.ts.map +1 -0
- package/dist/workbench/ExperimentsPanel.js +182 -0
- package/dist/workbench/FieldEditorPanel.d.ts +9 -0
- package/dist/workbench/FieldEditorPanel.d.ts.map +1 -0
- package/dist/workbench/FieldEditorPanel.js +650 -0
- package/dist/workbench/InspectorPanel.d.ts +6 -0
- package/dist/workbench/InspectorPanel.d.ts.map +1 -0
- package/dist/workbench/InspectorPanel.js +341 -0
- package/dist/workbench/PageNavigator.d.ts +6 -0
- package/dist/workbench/PageNavigator.d.ts.map +1 -0
- package/dist/workbench/PageNavigator.js +123 -0
- package/dist/workbench/SchemaPanel.d.ts +6 -0
- package/dist/workbench/SchemaPanel.d.ts.map +1 -0
- package/dist/workbench/SchemaPanel.js +222 -0
- package/dist/workbench/UserDataPanel.d.ts +6 -0
- package/dist/workbench/UserDataPanel.d.ts.map +1 -0
- package/dist/workbench/UserDataPanel.js +350 -0
- package/dist/workbench/WorkbenchApp.d.ts +6 -0
- package/dist/workbench/WorkbenchApp.d.ts.map +1 -0
- package/dist/workbench/WorkbenchApp.js +193 -0
- package/dist/workbench/cloudflare-worker/README.md +31 -0
- package/dist/workbench/cloudflare-worker/public/workbench.css +1614 -0
- package/dist/workbench/cloudflare-worker/public/workbench.js +77 -0
- package/dist/workbench/cloudflare-worker/worker.js +40 -0
- package/dist/workbench/cloudflare-worker/wrangler.toml +10 -0
- package/dist/workbench/index.d.ts +9 -0
- package/dist/workbench/index.d.ts.map +1 -0
- package/dist/workbench/index.js +44 -0
- package/dist/workbench/workbench.css +1614 -0
- package/dist/workbench/workbench.js +77 -0
- 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
|
+
}
|