form-page-builder 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/dist/index.js ADDED
@@ -0,0 +1,2402 @@
1
+ "use client";
2
+
3
+ // src/FormBuilder.tsx
4
+ import { useState as useState8 } from "react";
5
+ import { Loader2 as Loader22 } from "lucide-react";
6
+
7
+ // src/i18n/languages.ts
8
+ var DEFAULT_LANGUAGES = [
9
+ { code: "en", label: "EN" },
10
+ { code: "ja", label: "\u65E5\u672C\u8A9E" }
11
+ ];
12
+
13
+ // src/i18n/strings.ts
14
+ var DEFAULT_STRINGS = {
15
+ en: {
16
+ submit: "Submit",
17
+ selectPlaceholder: "Select...",
18
+ requiredError: "This field is required.",
19
+ invalidEmail: "Enter a valid email address.",
20
+ invalidPhone: "Enter a valid phone number.",
21
+ fixErrors: "Please fix the highlighted fields before submitting.",
22
+ submittedTitle: "Form submitted",
23
+ submittedBody: "Here's what would be sent to your backend:",
24
+ addFieldsHint: "Add fields in Build mode to see them here.",
25
+ close: "Close",
26
+ tooLong: (n) => `Must be ${n} characters or fewer.`,
27
+ tooSmall: (n) => `Must be at least ${n}.`,
28
+ tooLarge: (n) => `Must be at most ${n}.`
29
+ },
30
+ ja: {
31
+ submit: "\u9001\u4FE1",
32
+ selectPlaceholder: "\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044",
33
+ requiredError: "\u3053\u306E\u9805\u76EE\u306F\u5FC5\u9808\u3067\u3059\u3002",
34
+ invalidEmail: "\u6709\u52B9\u306A\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
35
+ invalidPhone: "\u6709\u52B9\u306A\u96FB\u8A71\u756A\u53F7\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
36
+ fixErrors: "\u5165\u529B\u5185\u5BB9\u3092\u3054\u78BA\u8A8D\u306E\u3046\u3048\u3001\u518D\u5EA6\u9001\u4FE1\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
37
+ submittedTitle: "\u9001\u4FE1\u5B8C\u4E86",
38
+ submittedBody: "\u30D0\u30C3\u30AF\u30A8\u30F3\u30C9\u306B\u9001\u4FE1\u3055\u308C\u308B\u5185\u5BB9\u306F\u4EE5\u4E0B\u306E\u901A\u308A\u3067\u3059\uFF1A",
39
+ addFieldsHint: "\u30D3\u30EB\u30C9\u30E2\u30FC\u30C9\u3067\u30D5\u30A3\u30FC\u30EB\u30C9\u3092\u8FFD\u52A0\u3059\u308B\u3068\u3001\u3053\u3053\u306B\u8868\u793A\u3055\u308C\u307E\u3059\u3002",
40
+ close: "\u9589\u3058\u308B",
41
+ tooLong: (n) => `${n}\u6587\u5B57\u4EE5\u5185\u3067\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`,
42
+ tooSmall: (n) => `${n}\u4EE5\u4E0A\u306E\u5024\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`,
43
+ tooLarge: (n) => `${n}\u4EE5\u4E0B\u306E\u5024\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
44
+ }
45
+ };
46
+
47
+ // src/i18n/chrome.ts
48
+ var CHROME = {
49
+ en: {
50
+ contentBlocks: "Content",
51
+ formFields: "Form Fields",
52
+ addingTo: "Adding to",
53
+ properties: "Properties",
54
+ selectFieldHint: "Select a field to edit its properties.",
55
+ label: "Label",
56
+ hideLabel: "Hide label",
57
+ inputType: "Input type",
58
+ content: "Content",
59
+ headingStyle: "Heading style",
60
+ typography: "Typography",
61
+ style: "Style",
62
+ align: "Align",
63
+ color: "Color",
64
+ imageUrl: "Image URL",
65
+ imageUrlHelper: "Upload the image on your own end (Drive, CDN, S3...) and paste the link here \u2014 this package doesn't host images.",
66
+ altText: "Alt text",
67
+ linkOptional: "Link (optional)",
68
+ shape: "Shape",
69
+ placeholder: "Placeholder",
70
+ maxLength: "Max length",
71
+ noLimit: "No limit",
72
+ minValue: "Min value",
73
+ maxValue: "Max value",
74
+ options: "Options",
75
+ addOption: "Add option",
76
+ defaultValue: "Default value",
77
+ defaultChecked: "Default checked",
78
+ checked: "Checked",
79
+ unchecked: "Unchecked",
80
+ none: "None",
81
+ requiredField: "Required field",
82
+ layout: "Layout",
83
+ width: "Width",
84
+ full: "Full",
85
+ labelPosition: "Label position",
86
+ above: "Above",
87
+ inline: "Inline",
88
+ verticalAlign: "Vertical align",
89
+ top: "Top",
90
+ middle: "Middle",
91
+ bottom: "Bottom",
92
+ icon: "Icon",
93
+ showIcon: "Show icon next to label",
94
+ deleteField: "Delete field",
95
+ build: "Build",
96
+ preview: "Preview",
97
+ newForm: "New",
98
+ myForms: "My forms",
99
+ save: "Save",
100
+ viewJson: "View JSON",
101
+ saving: "Saving...",
102
+ saved: "Saved",
103
+ saveFailed: "Save failed",
104
+ startNewForm: "Start a new form",
105
+ openSavedForm: "Open a saved form",
106
+ saveToLibraryTitle: "Save this form to your library",
107
+ sectionTitlePlaceholder: (n) => `Section ${n} title (optional)`,
108
+ noFieldsInSection: "No fields in this section yet \u2014 pick a field type on the left.",
109
+ addSection: "Add section",
110
+ moveUp: "Move up",
111
+ moveDown: "Move down",
112
+ duplicate: "Duplicate",
113
+ delete: "Delete",
114
+ deleteSection: "Delete section",
115
+ required: "required",
116
+ formJson: "Form JSON",
117
+ copy: "Copy",
118
+ copied: "Copied",
119
+ nothingSaved: 'Nothing saved yet. Click "Save" in the toolbar to store this form here.',
120
+ updated: "Updated",
121
+ open: "Open",
122
+ saveForm: "Save form",
123
+ formName: "Form name",
124
+ saveToLibrary: "Save to library",
125
+ loadingDraft: "Loading your draft...",
126
+ current: "current",
127
+ customColor: "Custom color",
128
+ fieldsCount: (n) => `${n} field${n === 1 ? "" : "s"}`,
129
+ formSettings: "Form settings",
130
+ submitLabel: "Submit button text",
131
+ submitMode: "Submit button",
132
+ combined: "Combined (one button)",
133
+ perSection: "Per section",
134
+ settings: "Form settings",
135
+ fieldTypeInput: "Input",
136
+ fieldTypeTextarea: "Textarea",
137
+ fieldTypeSelect: "Select",
138
+ fieldTypeRadio: "Radio group",
139
+ fieldTypeCheckboxGroup: "Checkbox group",
140
+ fieldTypeCheckbox: "Checkbox",
141
+ fieldTypeToggle: "Toggle",
142
+ fieldTypeParagraph: "Paragraph",
143
+ fieldTypeImage: "Image",
144
+ subtypeText: "Text",
145
+ subtypeEmail: "Email",
146
+ subtypePhone: "Phone",
147
+ subtypeNumber: "Number",
148
+ subtypePassword: "Password",
149
+ subtypeDate: "Date",
150
+ subtypeTime: "Time",
151
+ shapeSquare: "Square",
152
+ shapeCircle: "Circle",
153
+ shapeBanner: "Banner",
154
+ tagBody: "Body",
155
+ tagCaption: "Caption",
156
+ submitSectionTitle: "Submit",
157
+ submitStyle: "Submit button style",
158
+ buttonSize: "Size",
159
+ theme: "Theme",
160
+ primaryColor: "Primary color",
161
+ pageBackground: "Page background",
162
+ maxWidthPx: "Max width (px)",
163
+ reset: "Reset",
164
+ deviceLaptop: "Laptop",
165
+ deviceTablet: "Tablet",
166
+ deviceMobile: "Mobile",
167
+ untitledField: "Untitled field",
168
+ optionSeed: (n) => `Option ${n}`,
169
+ spacing: "Spacing",
170
+ spacingPagePadding: "Page padding",
171
+ spacingCanvasPadding: "Canvas padding",
172
+ spacingSectionGap: "Section gap",
173
+ spacingFieldGap: "Field gap",
174
+ spacingToolbarPadding: "Toolbar padding",
175
+ spacingPanelPadding: "Panel padding",
176
+ spacingTicketPadding: "Field padding"
177
+ },
178
+ ja: {
179
+ contentBlocks: "\u30B3\u30F3\u30C6\u30F3\u30C4",
180
+ formFields: "\u30D5\u30A9\u30FC\u30E0\u9805\u76EE",
181
+ addingTo: "\u8FFD\u52A0\u5148",
182
+ properties: "\u30D7\u30ED\u30D1\u30C6\u30A3",
183
+ selectFieldHint: "\u7DE8\u96C6\u3059\u308B\u30D5\u30A3\u30FC\u30EB\u30C9\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
184
+ label: "\u30E9\u30D9\u30EB",
185
+ hideLabel: "\u30E9\u30D9\u30EB\u3092\u975E\u8868\u793A",
186
+ inputType: "\u5165\u529B\u30BF\u30A4\u30D7",
187
+ content: "\u5185\u5BB9",
188
+ headingStyle: "\u898B\u51FA\u3057\u30B9\u30BF\u30A4\u30EB",
189
+ typography: "\u6587\u5B57\u30B9\u30BF\u30A4\u30EB",
190
+ style: "\u30B9\u30BF\u30A4\u30EB",
191
+ align: "\u914D\u7F6E",
192
+ color: "\u8272",
193
+ imageUrl: "\u753B\u50CFURL",
194
+ imageUrlHelper: "\u753B\u50CF\u306F\u5404\u81EA\u3067\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\uFF08Drive\u3001CDN\u3001S3\u306A\u3069\uFF09\u3057\u3001\u305D\u306EURL\u3092\u3053\u3053\u306B\u8CBC\u308A\u4ED8\u3051\u3066\u304F\u3060\u3055\u3044\u3002\u672C\u30D1\u30C3\u30B1\u30FC\u30B8\u306F\u753B\u50CF\u3092\u4FDD\u5B58\u3057\u307E\u305B\u3093\u3002",
195
+ altText: "\u4EE3\u66FF\u30C6\u30AD\u30B9\u30C8",
196
+ linkOptional: "\u30EA\u30F3\u30AF\uFF08\u4EFB\u610F\uFF09",
197
+ shape: "\u5F62\u72B6",
198
+ placeholder: "\u30D7\u30EC\u30FC\u30B9\u30DB\u30EB\u30C0\u30FC",
199
+ maxLength: "\u6700\u5927\u6587\u5B57\u6570",
200
+ noLimit: "\u5236\u9650\u306A\u3057",
201
+ minValue: "\u6700\u5C0F\u5024",
202
+ maxValue: "\u6700\u5927\u5024",
203
+ options: "\u9078\u629E\u80A2",
204
+ addOption: "\u9078\u629E\u80A2\u3092\u8FFD\u52A0",
205
+ defaultValue: "\u521D\u671F\u5024",
206
+ defaultChecked: "\u521D\u671F\u9078\u629E",
207
+ checked: "\u30AA\u30F3",
208
+ unchecked: "\u30AA\u30D5",
209
+ none: "\u306A\u3057",
210
+ requiredField: "\u5FC5\u9808\u9805\u76EE",
211
+ layout: "\u30EC\u30A4\u30A2\u30A6\u30C8",
212
+ width: "\u5E45",
213
+ full: "\u5168\u5E45",
214
+ labelPosition: "\u30E9\u30D9\u30EB\u4F4D\u7F6E",
215
+ above: "\u4E0A",
216
+ inline: "\u6A2A\u4E26\u3073",
217
+ verticalAlign: "\u5782\u76F4\u4F4D\u7F6E",
218
+ top: "\u4E0A",
219
+ middle: "\u4E2D\u592E",
220
+ bottom: "\u4E0B",
221
+ icon: "\u30A2\u30A4\u30B3\u30F3",
222
+ showIcon: "\u30E9\u30D9\u30EB\u6A2A\u306B\u30A2\u30A4\u30B3\u30F3\u3092\u8868\u793A",
223
+ deleteField: "\u30D5\u30A3\u30FC\u30EB\u30C9\u3092\u524A\u9664",
224
+ build: "\u4F5C\u6210",
225
+ preview: "\u30D7\u30EC\u30D3\u30E5\u30FC",
226
+ newForm: "\u65B0\u898F",
227
+ myForms: "\u30DE\u30A4\u30D5\u30A9\u30FC\u30E0",
228
+ save: "\u4FDD\u5B58",
229
+ viewJson: "JSON\u3092\u8868\u793A",
230
+ saving: "\u4FDD\u5B58\u4E2D...",
231
+ saved: "\u4FDD\u5B58\u6E08\u307F",
232
+ saveFailed: "\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
233
+ startNewForm: "\u65B0\u3057\u3044\u30D5\u30A9\u30FC\u30E0\u3092\u4F5C\u6210",
234
+ openSavedForm: "\u4FDD\u5B58\u6E08\u307F\u30D5\u30A9\u30FC\u30E0\u3092\u958B\u304F",
235
+ saveToLibraryTitle: "\u3053\u306E\u30D5\u30A9\u30FC\u30E0\u3092\u30E9\u30A4\u30D6\u30E9\u30EA\u306B\u4FDD\u5B58",
236
+ sectionTitlePlaceholder: (n) => `\u30BB\u30AF\u30B7\u30E7\u30F3${n}\u306E\u30BF\u30A4\u30C8\u30EB\uFF08\u4EFB\u610F\uFF09`,
237
+ noFieldsInSection: "\u3053\u306E\u30BB\u30AF\u30B7\u30E7\u30F3\u306B\u306F\u307E\u3060\u30D5\u30A3\u30FC\u30EB\u30C9\u304C\u3042\u308A\u307E\u305B\u3093\u3002\u5DE6\u306E\u30D1\u30CD\u30EB\u304B\u3089\u30D5\u30A3\u30FC\u30EB\u30C9\u3092\u9078\u3093\u3067\u304F\u3060\u3055\u3044\u3002",
238
+ addSection: "\u30BB\u30AF\u30B7\u30E7\u30F3\u3092\u8FFD\u52A0",
239
+ moveUp: "\u4E0A\u3078\u79FB\u52D5",
240
+ moveDown: "\u4E0B\u3078\u79FB\u52D5",
241
+ duplicate: "\u8907\u88FD",
242
+ delete: "\u524A\u9664",
243
+ deleteSection: "\u30BB\u30AF\u30B7\u30E7\u30F3\u3092\u524A\u9664",
244
+ required: "\u5FC5\u9808",
245
+ formJson: "\u30D5\u30A9\u30FC\u30E0JSON",
246
+ copy: "\u30B3\u30D4\u30FC",
247
+ copied: "\u30B3\u30D4\u30FC\u3057\u307E\u3057\u305F",
248
+ nothingSaved: "\u307E\u3060\u4FDD\u5B58\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002\u30C4\u30FC\u30EB\u30D0\u30FC\u306E\u300C\u4FDD\u5B58\u300D\u3092\u30AF\u30EA\u30C3\u30AF\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
249
+ updated: "\u66F4\u65B0\u65E5\u6642",
250
+ open: "\u958B\u304F",
251
+ saveForm: "\u30D5\u30A9\u30FC\u30E0\u3092\u4FDD\u5B58",
252
+ formName: "\u30D5\u30A9\u30FC\u30E0\u540D",
253
+ saveToLibrary: "\u30E9\u30A4\u30D6\u30E9\u30EA\u306B\u4FDD\u5B58",
254
+ loadingDraft: "\u4E0B\u66F8\u304D\u3092\u8AAD\u307F\u8FBC\u3093\u3067\u3044\u307E\u3059...",
255
+ current: "\u73FE\u5728\u7DE8\u96C6\u4E2D",
256
+ customColor: "\u30AB\u30B9\u30BF\u30E0\u30AB\u30E9\u30FC",
257
+ fieldsCount: (n) => `${n}\u4EF6\u306E\u30D5\u30A3\u30FC\u30EB\u30C9`,
258
+ formSettings: "\u30D5\u30A9\u30FC\u30E0\u8A2D\u5B9A",
259
+ submitLabel: "\u9001\u4FE1\u30DC\u30BF\u30F3\u306E\u30C6\u30AD\u30B9\u30C8",
260
+ submitMode: "\u9001\u4FE1\u30DC\u30BF\u30F3\u306E\u8868\u793A",
261
+ combined: "\u307E\u3068\u3081\u30661\u3064",
262
+ perSection: "\u30BB\u30AF\u30B7\u30E7\u30F3\u3054\u3068",
263
+ settings: "\u30D5\u30A9\u30FC\u30E0\u8A2D\u5B9A",
264
+ fieldTypeInput: "\u5165\u529B",
265
+ fieldTypeTextarea: "\u30C6\u30AD\u30B9\u30C8\u30A8\u30EA\u30A2",
266
+ fieldTypeSelect: "\u30BB\u30EC\u30AF\u30C8",
267
+ fieldTypeRadio: "\u30E9\u30B8\u30AA\u30DC\u30BF\u30F3",
268
+ fieldTypeCheckboxGroup: "\u30C1\u30A7\u30C3\u30AF\u30DC\u30C3\u30AF\u30B9\u30B0\u30EB\u30FC\u30D7",
269
+ fieldTypeCheckbox: "\u30C1\u30A7\u30C3\u30AF\u30DC\u30C3\u30AF\u30B9",
270
+ fieldTypeToggle: "\u30C8\u30B0\u30EB",
271
+ fieldTypeParagraph: "\u6BB5\u843D",
272
+ fieldTypeImage: "\u753B\u50CF",
273
+ subtypeText: "\u30C6\u30AD\u30B9\u30C8",
274
+ subtypeEmail: "\u30E1\u30FC\u30EB",
275
+ subtypePhone: "\u96FB\u8A71\u756A\u53F7",
276
+ subtypeNumber: "\u6570\u5024",
277
+ subtypePassword: "\u30D1\u30B9\u30EF\u30FC\u30C9",
278
+ subtypeDate: "\u65E5\u4ED8",
279
+ subtypeTime: "\u6642\u523B",
280
+ shapeSquare: "\u6B63\u65B9\u5F62",
281
+ shapeCircle: "\u5186\u5F62",
282
+ shapeBanner: "\u30D0\u30CA\u30FC",
283
+ tagBody: "\u672C\u6587",
284
+ tagCaption: "\u30AD\u30E3\u30D7\u30B7\u30E7\u30F3",
285
+ submitSectionTitle: "\u9001\u4FE1",
286
+ submitStyle: "\u9001\u4FE1\u30DC\u30BF\u30F3\u306E\u30B9\u30BF\u30A4\u30EB",
287
+ buttonSize: "\u30B5\u30A4\u30BA",
288
+ theme: "\u30C6\u30FC\u30DE",
289
+ primaryColor: "\u30E1\u30A4\u30F3\u30AB\u30E9\u30FC",
290
+ pageBackground: "\u30DA\u30FC\u30B8\u80CC\u666F\u8272",
291
+ maxWidthPx: "\u6700\u5927\u5E45\uFF08px\uFF09",
292
+ reset: "\u30EA\u30BB\u30C3\u30C8",
293
+ deviceLaptop: "\u30D1\u30BD\u30B3\u30F3",
294
+ deviceTablet: "\u30BF\u30D6\u30EC\u30C3\u30C8",
295
+ deviceMobile: "\u30E2\u30D0\u30A4\u30EB",
296
+ untitledField: "\u540D\u79F0\u672A\u8A2D\u5B9A\u306E\u30D5\u30A3\u30FC\u30EB\u30C9",
297
+ optionSeed: (n) => `\u9078\u629E\u80A2${n}`,
298
+ spacing: "\u4F59\u767D\u8A2D\u5B9A",
299
+ spacingPagePadding: "\u30DA\u30FC\u30B8\u4F59\u767D",
300
+ spacingCanvasPadding: "\u30AD\u30E3\u30F3\u30D0\u30B9\u4F59\u767D",
301
+ spacingSectionGap: "\u30BB\u30AF\u30B7\u30E7\u30F3\u9593\u9694",
302
+ spacingFieldGap: "\u30D5\u30A3\u30FC\u30EB\u30C9\u9593\u9694",
303
+ spacingToolbarPadding: "\u30C4\u30FC\u30EB\u30D0\u30FC\u4F59\u767D",
304
+ spacingPanelPadding: "\u30D1\u30CD\u30EB\u4F59\u767D",
305
+ spacingTicketPadding: "\u30D5\u30A3\u30FC\u30EB\u30C9\u5185\u4F59\u767D"
306
+ }
307
+ };
308
+
309
+ // src/lib/bilingual.ts
310
+ function bi(en = "", ja = "") {
311
+ return { en, ja };
312
+ }
313
+ function t(val, lang) {
314
+ if (val && typeof val === "object") return val[lang] || val.en || "";
315
+ return val || "";
316
+ }
317
+ function withLang(val, lang, value) {
318
+ const base = val && typeof val === "object" ? val : bi(typeof val === "string" ? val : "");
319
+ return { ...base, [lang]: value };
320
+ }
321
+
322
+ // src/lib/storage/localStorageAdapter.ts
323
+ function hasLocalStorage() {
324
+ return typeof window !== "undefined" && !!window.localStorage;
325
+ }
326
+ var localStorageAdapter = {
327
+ async get(key) {
328
+ if (!hasLocalStorage()) return null;
329
+ return window.localStorage.getItem(key);
330
+ },
331
+ async set(key, value) {
332
+ if (!hasLocalStorage()) return;
333
+ window.localStorage.setItem(key, value);
334
+ },
335
+ async delete(key) {
336
+ if (!hasLocalStorage()) return;
337
+ window.localStorage.removeItem(key);
338
+ }
339
+ };
340
+
341
+ // src/hooks/useTheme.ts
342
+ import { useState } from "react";
343
+
344
+ // src/theme/defaultTheme.ts
345
+ var DEFAULT_THEME = {
346
+ primary: "#5B5FEF",
347
+ primarySoft: "#EEF0FF",
348
+ danger: "#C4432E",
349
+ dangerSoft: "#FDECE4",
350
+ ink: "#1B1E24",
351
+ muted: "#9296A3",
352
+ border: "#E2E4E9",
353
+ surface: "#FFFFFF",
354
+ canvas: "#F5F6F8",
355
+ pageBackground: "#FFFFFF",
356
+ layout: { maxWidth: 640, pagePadding: 28, canvasPadding: 20, sectionGap: 20, fieldGap: 16, toolbarPadding: 10, panelPadding: 14, ticketPadding: 14 }
357
+ };
358
+
359
+ // src/hooks/useTheme.ts
360
+ function useTheme(themeOverrideProp) {
361
+ const baseTheme = {
362
+ ...DEFAULT_THEME,
363
+ ...themeOverrideProp || {},
364
+ layout: { ...DEFAULT_THEME.layout, ...themeOverrideProp && themeOverrideProp.layout || {} }
365
+ };
366
+ const [themeOverrides, setThemeOverrides] = useState({});
367
+ const theme = {
368
+ ...baseTheme,
369
+ ...themeOverrides,
370
+ layout: { ...baseTheme.layout, ...themeOverrides.layout || {} }
371
+ };
372
+ function updateThemeColor(key, value) {
373
+ setThemeOverrides((prev) => ({ ...prev, [key]: value }));
374
+ }
375
+ function updateThemeLayout(key, value) {
376
+ setThemeOverrides((prev) => ({ ...prev, layout: { ...prev.layout, [key]: value } }));
377
+ }
378
+ function resetTheme() {
379
+ setThemeOverrides({});
380
+ }
381
+ function replaceThemeOverrides(overrides) {
382
+ setThemeOverrides(overrides);
383
+ }
384
+ return { theme, themeOverrides, updateThemeColor, updateThemeLayout, resetTheme, replaceThemeOverrides };
385
+ }
386
+
387
+ // src/hooks/useFormDocument.ts
388
+ import { useState as useState2 } from "react";
389
+
390
+ // src/constants/fieldTypes.ts
391
+ import {
392
+ Type,
393
+ AlignLeft,
394
+ ChevronDown,
395
+ Circle,
396
+ ListChecks,
397
+ CheckSquare,
398
+ ToggleLeft,
399
+ FileText,
400
+ Image as ImageIcon
401
+ } from "lucide-react";
402
+ var FIELD_TYPES = [
403
+ { type: "paragraph", icon: FileText, isContent: true, defaultIcon: "FileText" },
404
+ { type: "image", icon: ImageIcon, isContent: true, isImage: true, defaultIcon: "Type" },
405
+ { type: "input", icon: Type, placeholder: true, hasSubtype: true, defaultIcon: "Type" },
406
+ { type: "textarea", icon: AlignLeft, placeholder: true, defaultIcon: "AlignLeft" },
407
+ { type: "select", icon: ChevronDown, options: true, defaultIcon: "ChevronDown" },
408
+ { type: "radio", icon: Circle, options: true, defaultIcon: "Circle" },
409
+ { type: "checkboxGroup", icon: ListChecks, options: true, multiValue: true, defaultIcon: "ListChecks" },
410
+ { type: "checkbox", icon: CheckSquare, boolean: true, defaultIcon: "CheckSquare" },
411
+ { type: "toggle", icon: ToggleLeft, boolean: true, defaultIcon: "ToggleLeft" }
412
+ ];
413
+ var TYPE_MAP = Object.fromEntries(FIELD_TYPES.map((f) => [f.type, f]));
414
+ var CONTENT_TYPES = FIELD_TYPES.filter((f) => f.isContent);
415
+ var FORM_TYPES = FIELD_TYPES.filter((f) => !f.isContent);
416
+ function getMeta(type) {
417
+ return TYPE_MAP[type] || TYPE_MAP.input;
418
+ }
419
+ var LEGACY_INPUT_TYPES = ["text", "email", "phone", "number", "password", "date"];
420
+ var INPUT_SUBTYPES = ["text", "email", "phone", "number", "password", "date", "time"];
421
+ var IMAGE_SHAPES = ["square", "circle", "banner"];
422
+ var FIELD_TYPE_CHROME_KEY = {
423
+ paragraph: "fieldTypeParagraph",
424
+ image: "fieldTypeImage",
425
+ input: "fieldTypeInput",
426
+ textarea: "fieldTypeTextarea",
427
+ select: "fieldTypeSelect",
428
+ radio: "fieldTypeRadio",
429
+ checkboxGroup: "fieldTypeCheckboxGroup",
430
+ checkbox: "fieldTypeCheckbox",
431
+ toggle: "fieldTypeToggle"
432
+ };
433
+ var INPUT_SUBTYPE_CHROME_KEY = {
434
+ text: "subtypeText",
435
+ email: "subtypeEmail",
436
+ phone: "subtypePhone",
437
+ number: "subtypeNumber",
438
+ password: "subtypePassword",
439
+ date: "subtypeDate",
440
+ time: "subtypeTime"
441
+ };
442
+ var IMAGE_SHAPE_CHROME_KEY = {
443
+ square: "shapeSquare",
444
+ circle: "shapeCircle",
445
+ banner: "shapeBanner"
446
+ };
447
+
448
+ // src/lib/id.ts
449
+ var genFormId = () => `form_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 7)}`;
450
+ var genSectionId = () => `section_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 7)}`;
451
+ var idCounter = 1;
452
+ var nextId = () => `field_${idCounter++}`;
453
+ function resyncIdCounter(fields) {
454
+ let max = 0;
455
+ fields.forEach((f) => {
456
+ const m = /^field_(\d+)$/.exec(f.id);
457
+ if (m) max = Math.max(max, parseInt(m[1], 10));
458
+ });
459
+ idCounter = Math.max(idCounter, max + 1);
460
+ }
461
+
462
+ // src/lib/fieldDefaults.ts
463
+ function defaultFieldFor(type, language, chrome) {
464
+ const meta = getMeta(type);
465
+ const seedLabel = chrome[FIELD_TYPE_CHROME_KEY[type]] || type;
466
+ const base = {
467
+ id: nextId(),
468
+ type,
469
+ label: withLang(bi(), language, seedLabel),
470
+ hideLabel: false,
471
+ width: "1/1",
472
+ verticalAlign: "top",
473
+ labelPosition: "top",
474
+ showIcon: false,
475
+ displayIcon: meta.defaultIcon
476
+ };
477
+ if (!meta.isContent) base.required = false;
478
+ if (meta.hasSubtype) base.inputType = "text";
479
+ if (meta.placeholder) base.placeholder = bi();
480
+ if (meta.options) {
481
+ base.options = [
482
+ { label: withLang(bi(), language, chrome.optionSeed(1)), value: "option_1" },
483
+ { label: withLang(bi(), language, chrome.optionSeed(2)), value: "option_2" }
484
+ ];
485
+ }
486
+ if (meta.isImage) {
487
+ base.src = "";
488
+ base.alt = bi();
489
+ base.link = "";
490
+ base.shape = "square";
491
+ } else if (meta.boolean) {
492
+ base.defaultValue = false;
493
+ } else if (meta.multiValue) {
494
+ base.defaultValue = [];
495
+ } else if (type === "paragraph") {
496
+ base.content = bi();
497
+ base.tag = "p";
498
+ base.fontSize = "md";
499
+ base.fontWeight = "normal";
500
+ base.fontStyle = "normal";
501
+ base.textAlign = "left";
502
+ base.color = "";
503
+ } else {
504
+ base.defaultValue = "";
505
+ }
506
+ return base;
507
+ }
508
+ function defaultSection(titleEn = "") {
509
+ return { id: genSectionId(), title: bi(titleEn, ""), background: "", collapsed: false, submitStyle: null, fields: [] };
510
+ }
511
+
512
+ // src/hooks/useFormDocument.ts
513
+ function useFormDocument({ language, chrome }) {
514
+ const [title, setTitle] = useState2(bi("Untitled form", ""));
515
+ const [submitLabel, setSubmitLabelState] = useState2(bi());
516
+ const [submitMode, setSubmitMode] = useState2("combined");
517
+ const [submitStyle, setSubmitStyleState] = useState2({ color: "", size: "md" });
518
+ const [sections, setSections] = useState2([defaultSection()]);
519
+ const [activeSectionId, setActiveSectionId] = useState2(null);
520
+ const [selectedId, setSelectedId] = useState2(null);
521
+ const allFields = sections.flatMap((s) => s.fields);
522
+ const selected = allFields.find((f) => f.id === selectedId) || null;
523
+ const activeSection = sections.find((s) => s.id === activeSectionId) || sections[0] || defaultSection();
524
+ function updateTitle(value) {
525
+ setTitle((prev) => withLang(prev, language, value));
526
+ }
527
+ function updateSubmitLabel(value) {
528
+ setSubmitLabelState((prev) => withLang(prev, language, value));
529
+ }
530
+ function updateSubmitStyle(patch) {
531
+ setSubmitStyleState((prev) => ({ ...prev, ...patch }));
532
+ }
533
+ function addSection() {
534
+ const s = defaultSection();
535
+ setSections((prev) => [...prev, s]);
536
+ setActiveSectionId(s.id);
537
+ }
538
+ function duplicateSection(sectionId) {
539
+ const newSectionId = genSectionId();
540
+ setSections((prev) => {
541
+ const idx = prev.findIndex((s) => s.id === sectionId);
542
+ if (idx === -1) return prev;
543
+ const copy = { ...prev[idx], id: newSectionId, fields: prev[idx].fields.map((f) => ({ ...f, id: nextId() })) };
544
+ const next = [...prev];
545
+ next.splice(idx + 1, 0, copy);
546
+ return next;
547
+ });
548
+ setActiveSectionId(newSectionId);
549
+ }
550
+ function deleteSection(sectionId) {
551
+ setSections((prev) => {
552
+ if (prev.length <= 1) return prev;
553
+ const next = prev.filter((s) => s.id !== sectionId);
554
+ setActiveSectionId((curr) => curr === sectionId ? next[0].id : curr);
555
+ return next;
556
+ });
557
+ }
558
+ function moveSection(sectionId, dir) {
559
+ setSections((prev) => {
560
+ const idx = prev.findIndex((s) => s.id === sectionId);
561
+ const swap = idx + dir;
562
+ if (swap < 0 || swap >= prev.length) return prev;
563
+ const next = [...prev];
564
+ [next[idx], next[swap]] = [next[swap], next[idx]];
565
+ return next;
566
+ });
567
+ }
568
+ function updateSectionTitle(sectionId, value) {
569
+ setSections((prev) => prev.map((s) => s.id === sectionId ? { ...s, title: withLang(s.title, language, value) } : s));
570
+ }
571
+ function updateSectionBackground(sectionId, color) {
572
+ setSections((prev) => prev.map((s) => s.id === sectionId ? { ...s, background: color } : s));
573
+ }
574
+ function toggleSectionCollapse(sectionId) {
575
+ setSections((prev) => prev.map((s) => s.id === sectionId ? { ...s, collapsed: !s.collapsed } : s));
576
+ }
577
+ function updateSectionSubmitStyle(sectionId, patch) {
578
+ setSections((prev) => prev.map((s) => s.id === sectionId ? { ...s, submitStyle: { ...s.submitStyle || submitStyle, ...patch } } : s));
579
+ }
580
+ function clearSectionSubmitStyle(sectionId) {
581
+ setSections((prev) => prev.map((s) => s.id === sectionId ? { ...s, submitStyle: null } : s));
582
+ }
583
+ function addField(type) {
584
+ const field = defaultFieldFor(type, language, chrome);
585
+ const targetId = activeSectionId || sections[0]?.id;
586
+ if (!targetId) return;
587
+ setSections((prev) => prev.map((s) => s.id === targetId ? { ...s, fields: [...s.fields, field] } : s));
588
+ setSelectedId(field.id);
589
+ }
590
+ function updateField(fieldId, patch) {
591
+ setSections((prev) => prev.map((s) => ({ ...s, fields: s.fields.map((f) => f.id === fieldId ? { ...f, ...patch } : f) })));
592
+ }
593
+ function deleteField(fieldId) {
594
+ setSections((prev) => prev.map((s) => ({ ...s, fields: s.fields.filter((f) => f.id !== fieldId) })));
595
+ if (selectedId === fieldId) setSelectedId(null);
596
+ }
597
+ function duplicateField(fieldId) {
598
+ const newId = nextId();
599
+ setSections((prev) => prev.map((s) => {
600
+ const idx = s.fields.findIndex((f) => f.id === fieldId);
601
+ if (idx === -1) return s;
602
+ const copy = { ...s.fields[idx], id: newId };
603
+ const fields = [...s.fields];
604
+ fields.splice(idx + 1, 0, copy);
605
+ return { ...s, fields };
606
+ }));
607
+ setSelectedId(newId);
608
+ }
609
+ function moveField(fieldId, dir) {
610
+ setSections((prev) => prev.map((s) => {
611
+ const idx = s.fields.findIndex((f) => f.id === fieldId);
612
+ if (idx === -1) return s;
613
+ const swap = idx + dir;
614
+ if (swap < 0 || swap >= s.fields.length) return s;
615
+ const fields = [...s.fields];
616
+ [fields[idx], fields[swap]] = [fields[swap], fields[idx]];
617
+ return { ...s, fields };
618
+ }));
619
+ }
620
+ function reorderWithinSection(sectionId, fromIdx, toIdx) {
621
+ if (fromIdx === toIdx || fromIdx == null || toIdx == null) return;
622
+ setSections((prev) => prev.map((s) => {
623
+ if (s.id !== sectionId) return s;
624
+ const fields = [...s.fields];
625
+ const [moved] = fields.splice(fromIdx, 1);
626
+ fields.splice(toIdx, 0, moved);
627
+ return { ...s, fields };
628
+ }));
629
+ }
630
+ function updateOption(fieldId, optIdx, patch) {
631
+ setSections((prev) => prev.map((s) => ({
632
+ ...s,
633
+ fields: s.fields.map((f) => {
634
+ if (f.id !== fieldId || !("options" in f) || !f.options) return f;
635
+ return { ...f, options: f.options.map((o, i) => i === optIdx ? { ...o, ...patch } : o) };
636
+ })
637
+ })));
638
+ }
639
+ function addOption(fieldId) {
640
+ setSections((prev) => prev.map((s) => ({
641
+ ...s,
642
+ fields: s.fields.map((f) => {
643
+ if (f.id !== fieldId || !("options" in f) || !f.options) return f;
644
+ const n = f.options.length + 1;
645
+ return { ...f, options: [...f.options, { label: withLang(bi(), language, chrome.optionSeed(n)), value: `option_${n}` }] };
646
+ })
647
+ })));
648
+ }
649
+ function removeOption(fieldId, optIdx) {
650
+ setSections((prev) => prev.map((s) => ({
651
+ ...s,
652
+ fields: s.fields.map((f) => f.id !== fieldId || !("options" in f) || !f.options ? f : { ...f, options: f.options.filter((_, i) => i !== optIdx) })
653
+ })));
654
+ }
655
+ function loadDocument(doc) {
656
+ resyncIdCounter(doc.sections.flatMap((s) => s.fields));
657
+ setSections(doc.sections);
658
+ setActiveSectionId(doc.sections[0]?.id || null);
659
+ setTitle(doc.title);
660
+ setSubmitLabelState(doc.submitLabel);
661
+ setSubmitMode(doc.submitMode);
662
+ setSubmitStyleState(doc.submitStyle);
663
+ setSelectedId(null);
664
+ }
665
+ function resetToBlank() {
666
+ const s = defaultSection();
667
+ setSections([s]);
668
+ setActiveSectionId(s.id);
669
+ setTitle(bi("Untitled form", ""));
670
+ setSubmitLabelState(bi());
671
+ setSubmitMode("combined");
672
+ setSubmitStyleState({ color: "", size: "md" });
673
+ setSelectedId(null);
674
+ }
675
+ return {
676
+ title,
677
+ submitLabel,
678
+ submitMode,
679
+ submitStyle,
680
+ sections,
681
+ activeSectionId,
682
+ selectedId,
683
+ allFields,
684
+ selected,
685
+ activeSection,
686
+ setActiveSectionId,
687
+ setSelectedId,
688
+ setSubmitMode,
689
+ setTitle,
690
+ updateTitle,
691
+ updateSubmitLabel,
692
+ updateSubmitStyle,
693
+ addSection,
694
+ duplicateSection,
695
+ deleteSection,
696
+ moveSection,
697
+ updateSectionTitle,
698
+ updateSectionBackground,
699
+ toggleSectionCollapse,
700
+ updateSectionSubmitStyle,
701
+ clearSectionSubmitStyle,
702
+ addField,
703
+ updateField,
704
+ deleteField,
705
+ duplicateField,
706
+ moveField,
707
+ reorderWithinSection,
708
+ updateOption,
709
+ addOption,
710
+ removeOption,
711
+ loadDocument,
712
+ resetToBlank
713
+ };
714
+ }
715
+
716
+ // src/hooks/usePersistence.ts
717
+ import { useEffect, useRef, useState as useState3 } from "react";
718
+
719
+ // src/lib/storage/keys.ts
720
+ var DRAFT_KEY = "form-page-builder:draft";
721
+ var INDEX_KEY = "form-page-builder:index";
722
+ var formKey = (id) => `form-page-builder:saved:${id}`;
723
+
724
+ // src/lib/migrate.ts
725
+ function migrateField(field) {
726
+ let f = { ...field };
727
+ if (LEGACY_INPUT_TYPES.includes(f.type)) f = { ...f, type: "input", inputType: f.type };
728
+ f.label = typeof f.label === "string" ? bi(f.label) : f.label || bi();
729
+ f.hideLabel = !!f.hideLabel;
730
+ if ("placeholder" in f) f.placeholder = typeof f.placeholder === "string" ? bi(f.placeholder) : f.placeholder || bi();
731
+ if ("content" in f) f.content = typeof f.content === "string" ? bi(f.content) : f.content || bi();
732
+ if ("alt" in f) f.alt = typeof f.alt === "string" ? bi(f.alt) : f.alt || bi();
733
+ if (f.options) f.options = f.options.map((o) => ({ ...o, label: typeof o.label === "string" ? bi(o.label) : o.label || bi() }));
734
+ if (f.type === "paragraph") {
735
+ f.tag = f.tag || "p";
736
+ f.fontSize = f.fontSize || "md";
737
+ f.fontWeight = f.fontWeight || "normal";
738
+ f.fontStyle = f.fontStyle || "normal";
739
+ f.textAlign = f.textAlign || "left";
740
+ f.color = f.color || "";
741
+ }
742
+ f.verticalAlign = f.verticalAlign || "top";
743
+ return f;
744
+ }
745
+ function migrateFields(fields) {
746
+ return (fields || []).map(migrateField);
747
+ }
748
+ function migrateDocument(raw) {
749
+ if (!raw) return null;
750
+ const title = typeof raw.title === "string" ? bi(raw.title) : raw.title || bi();
751
+ const submitLabel = typeof raw.submitLabel === "string" ? bi(raw.submitLabel) : raw.submitLabel || bi();
752
+ const submitMode = raw.submitMode === "perSection" ? "perSection" : "combined";
753
+ const submitStyle = raw.submitStyle || { color: "", size: "md" };
754
+ const themeOverrides = raw.themeOverrides || {};
755
+ if (raw.sections) {
756
+ return {
757
+ title,
758
+ submitLabel,
759
+ submitMode,
760
+ submitStyle,
761
+ themeOverrides,
762
+ sections: raw.sections.map((s) => ({
763
+ id: s.id || genSectionId(),
764
+ title: typeof s.title === "string" ? bi(s.title) : s.title || bi(),
765
+ background: s.background || "",
766
+ collapsed: !!s.collapsed,
767
+ submitStyle: s.submitStyle || null,
768
+ fields: migrateFields(s.fields || [])
769
+ }))
770
+ };
771
+ }
772
+ return { title, submitLabel, submitMode, submitStyle, themeOverrides, sections: [{ ...defaultSection(), fields: migrateFields(raw.fields || []) }] };
773
+ }
774
+
775
+ // src/hooks/usePersistence.ts
776
+ function usePersistence({
777
+ storage,
778
+ language,
779
+ document,
780
+ onLoadDocument,
781
+ onLoadThemeOverrides,
782
+ onTitleChange,
783
+ onNewForm,
784
+ ensureActiveSection
785
+ }) {
786
+ const [currentFormId, setCurrentFormId] = useState3(null);
787
+ const [loadingDraft, setLoadingDraft] = useState3(true);
788
+ const [saveState, setSaveState] = useState3("idle");
789
+ const [savedForms, setSavedForms] = useState3([]);
790
+ const [saveAsPrompt, setSaveAsPrompt] = useState3(null);
791
+ const autosaveTimer = useRef(null);
792
+ const hasLoadedOnce = useRef(false);
793
+ async function refreshLibrary() {
794
+ try {
795
+ const raw = await storage.get(INDEX_KEY);
796
+ const list = raw ? JSON.parse(raw) : [];
797
+ setSavedForms(list.sort((a, b) => b.updatedAt - a.updatedAt));
798
+ } catch (err) {
799
+ setSavedForms([]);
800
+ }
801
+ }
802
+ useEffect(() => {
803
+ (async () => {
804
+ try {
805
+ const raw = await storage.get(DRAFT_KEY);
806
+ if (raw) {
807
+ const parsed = JSON.parse(raw);
808
+ const doc = migrateDocument(parsed);
809
+ if (doc) {
810
+ onLoadDocument(doc);
811
+ onLoadThemeOverrides(doc.themeOverrides);
812
+ }
813
+ if (parsed.currentFormId) setCurrentFormId(parsed.currentFormId);
814
+ } else {
815
+ ensureActiveSection();
816
+ }
817
+ } catch (err) {
818
+ ensureActiveSection();
819
+ } finally {
820
+ setLoadingDraft(false);
821
+ hasLoadedOnce.current = true;
822
+ }
823
+ })();
824
+ refreshLibrary();
825
+ }, []);
826
+ useEffect(() => {
827
+ if (!hasLoadedOnce.current) return;
828
+ setSaveState("saving");
829
+ if (autosaveTimer.current) clearTimeout(autosaveTimer.current);
830
+ autosaveTimer.current = setTimeout(async () => {
831
+ try {
832
+ await storage.set(DRAFT_KEY, JSON.stringify({ ...document, currentFormId }));
833
+ setSaveState("saved");
834
+ } catch (err) {
835
+ setSaveState("error");
836
+ }
837
+ }, 600);
838
+ return () => {
839
+ if (autosaveTimer.current) clearTimeout(autosaveTimer.current);
840
+ };
841
+ }, [document.title, document.submitLabel, document.submitMode, document.submitStyle, document.themeOverrides, document.sections, currentFormId]);
842
+ async function saveAs(name) {
843
+ const id = genFormId();
844
+ const now = Date.now();
845
+ try {
846
+ const newTitle = bi(name, "");
847
+ await storage.set(formKey(id), JSON.stringify({ ...document, title: newTitle, id, updatedAt: now }));
848
+ const next = [...savedForms, { id, title: name, updatedAt: now }];
849
+ await storage.set(INDEX_KEY, JSON.stringify(next));
850
+ setSavedForms(next);
851
+ setCurrentFormId(id);
852
+ onTitleChange(newTitle);
853
+ setSaveAsPrompt(null);
854
+ } catch (err) {
855
+ alert("Couldn't save the form. Please try again.");
856
+ }
857
+ }
858
+ async function saveExisting() {
859
+ if (!currentFormId) {
860
+ setSaveAsPrompt({ open: true, suggestedName: t(document.title, language) });
861
+ return;
862
+ }
863
+ const now = Date.now();
864
+ try {
865
+ await storage.set(formKey(currentFormId), JSON.stringify({ ...document, id: currentFormId, updatedAt: now }));
866
+ const next = savedForms.map((f) => f.id === currentFormId ? { ...f, title: t(document.title, "en"), updatedAt: now } : f);
867
+ setSavedForms(next);
868
+ await storage.set(INDEX_KEY, JSON.stringify(next));
869
+ } catch (err) {
870
+ alert("Couldn't save the form. Please try again.");
871
+ }
872
+ }
873
+ async function loadForm(id) {
874
+ try {
875
+ const raw = await storage.get(formKey(id));
876
+ if (!raw) return;
877
+ const doc = migrateDocument(JSON.parse(raw));
878
+ if (!doc) return;
879
+ onLoadDocument(doc);
880
+ onLoadThemeOverrides(doc.themeOverrides);
881
+ setCurrentFormId(id);
882
+ } catch (err) {
883
+ alert("Couldn't load that form.");
884
+ }
885
+ }
886
+ async function deleteForm(id) {
887
+ try {
888
+ await storage.delete(formKey(id));
889
+ const next = savedForms.filter((f) => f.id !== id);
890
+ setSavedForms(next);
891
+ await storage.set(INDEX_KEY, JSON.stringify(next));
892
+ if (currentFormId === id) setCurrentFormId(null);
893
+ } catch (err) {
894
+ }
895
+ }
896
+ function newForm() {
897
+ onNewForm();
898
+ setCurrentFormId(null);
899
+ }
900
+ function dismissSaveAsPrompt() {
901
+ setSaveAsPrompt(null);
902
+ }
903
+ return {
904
+ loadingDraft,
905
+ saveState,
906
+ savedForms,
907
+ currentFormId,
908
+ saveAs,
909
+ saveExisting,
910
+ loadForm,
911
+ deleteForm,
912
+ refreshLibrary,
913
+ newForm,
914
+ saveAsPrompt,
915
+ dismissSaveAsPrompt
916
+ };
917
+ }
918
+
919
+ // src/hooks/useDragReorder.ts
920
+ import { useRef as useRef2, useState as useState4 } from "react";
921
+ function useDragReorder(reorderWithinSection) {
922
+ const dragRef = useRef2({ sectionId: null, index: null });
923
+ const [dragOverKey, setDragOverKey] = useState4(null);
924
+ function getDropZoneHandlers(sectionId, index) {
925
+ const dragKey = `${sectionId}:${index}`;
926
+ return {
927
+ onDragOver: (e) => {
928
+ e.preventDefault();
929
+ setDragOverKey(dragKey);
930
+ },
931
+ onDragLeave: () => setDragOverKey(null),
932
+ onDrop: () => {
933
+ if (dragRef.current.sectionId === sectionId) reorderWithinSection(sectionId, dragRef.current.index, index);
934
+ dragRef.current = { sectionId: null, index: null };
935
+ setDragOverKey(null);
936
+ }
937
+ };
938
+ }
939
+ function getDragHandleProps(sectionId, index) {
940
+ return {
941
+ draggable: true,
942
+ onDragStart: () => {
943
+ dragRef.current = { sectionId, index };
944
+ }
945
+ };
946
+ }
947
+ return { dragOverKey, getDropZoneHandlers, getDragHandleProps };
948
+ }
949
+
950
+ // src/components/Toolbar.tsx
951
+ import { Loader2, AlertCircle, Languages, Pencil, Eye, Settings, FilePlus2, FolderOpen, Save, Code2 } from "lucide-react";
952
+
953
+ // src/styles/styles.ts
954
+ var styles = {
955
+ app: { fontFamily: "'Inter', -apple-system, BlinkMacSystemFont, sans-serif", background: "var(--fb-canvas)", borderRadius: 12, overflow: "hidden", border: "1px solid var(--fb-border)", color: "var(--fb-ink)", minHeight: 640, maxHeight: "100vh", display: "flex", flexDirection: "column" },
956
+ toolbar: { display: "flex", alignItems: "center", justifyContent: "space-between", padding: "var(--fb-space-toolbar) 16px", borderBottom: "1px solid var(--fb-border)", background: "var(--fb-surface)", flexWrap: "wrap", gap: 8, position: "relative" },
957
+ toolbarLeft: { display: "flex", alignItems: "center", gap: 10 },
958
+ logoMark: { width: 26, height: 26, borderRadius: 7, background: "var(--fb-primary)", color: "#fff", fontSize: 11, fontWeight: 700, display: "flex", alignItems: "center", justifyContent: "center", fontFamily: "'JetBrains Mono', monospace", letterSpacing: "-0.5px" },
959
+ titleInput: { border: "none", outline: "none", fontSize: 14, fontWeight: 600, background: "transparent", color: "var(--fb-ink)", padding: "4px 6px", borderRadius: 6, minWidth: 160 },
960
+ toolbarRight: { display: "flex", alignItems: "center", gap: 6, flexWrap: "wrap" },
961
+ toolbarDivider: { width: 1, height: 20, background: "var(--fb-border)", margin: "0 4px" },
962
+ tabBtn: { display: "flex", alignItems: "center", gap: 6, padding: "6px 12px", borderRadius: 7, border: "1px solid transparent", background: "transparent", color: "#6B6E79", fontSize: 13, fontWeight: 500 },
963
+ tabBtnActive: { display: "flex", alignItems: "center", gap: 6, padding: "6px 12px", borderRadius: 7, border: "1px solid var(--fb-border)", background: "var(--fb-primary-soft)", color: "var(--fb-primary)", fontSize: 13, fontWeight: 600 },
964
+ ghostBtn: { display: "flex", alignItems: "center", gap: 6, padding: "6px 12px", borderRadius: 7, border: "1px solid var(--fb-border)", background: "var(--fb-surface)", color: "#4A4D57", fontSize: 13, fontWeight: 500 },
965
+ primaryBtn: { display: "flex", alignItems: "center", gap: 6, padding: "6px 13px", borderRadius: 7, border: "1px solid var(--fb-primary)", background: "var(--fb-primary)", color: "#fff", fontSize: 13, fontWeight: 600 },
966
+ saveStatus: { display: "flex", alignItems: "center", gap: 5, fontSize: 12, color: "var(--fb-muted)", minWidth: 60 },
967
+ countBadge: { fontSize: 10.5, fontWeight: 700, background: "var(--fb-primary-soft)", color: "var(--fb-primary)", borderRadius: 9, padding: "1px 6px", marginLeft: 2 },
968
+ loadingScreen: { flex: 1, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", color: "var(--fb-muted)" },
969
+ libraryBody: { padding: "var(--fb-space-panel)", overflowY: "auto", display: "flex", flexDirection: "column", gap: 4 },
970
+ libraryRow: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 10, padding: "10px 8px", borderRadius: 8, border: "1px solid #F0F1F4" },
971
+ libraryRowTitle: { fontSize: 13.5, fontWeight: 600, color: "var(--fb-ink)", display: "flex", alignItems: "center", gap: 8, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
972
+ libraryRowMeta: { fontSize: 11.5, color: "var(--fb-muted)", marginTop: 2 },
973
+ currentBadge: { fontSize: 10, fontWeight: 700, color: "var(--fb-primary)", background: "var(--fb-primary-soft)", padding: "2px 6px", borderRadius: 5, flexShrink: 0 },
974
+ workArea: { display: "flex", flex: 1, minHeight: 0 },
975
+ palette: { width: 200, borderRight: "1px solid var(--fb-border)", background: "var(--fb-surface)", padding: "var(--fb-space-panel)", overflowY: "auto" },
976
+ panelHeading: { fontSize: 11, fontWeight: 700, letterSpacing: "0.06em", textTransform: "uppercase", color: "var(--fb-muted)", marginBottom: 10 },
977
+ activeSectionHint: { fontSize: 11.5, color: "var(--fb-muted)", marginBottom: 14, lineHeight: 1.4 },
978
+ paletteList: { display: "flex", flexDirection: "column", gap: 4 },
979
+ paletteItem: { display: "flex", alignItems: "center", gap: 9, padding: "8px 9px", borderRadius: 8, border: "1px solid transparent", background: "transparent", fontSize: 13, color: "#33353E", textAlign: "left" },
980
+ canvas: { flex: 1, padding: "var(--fb-space-canvas)", overflowY: "auto", background: "var(--fb-canvas)" },
981
+ sectionBlock: { borderRadius: 12, padding: "var(--fb-space-panel)", marginBottom: "var(--fb-space-section)", transition: "border-color 0.12s" },
982
+ sectionHeader: { display: "flex", alignItems: "center", gap: 8, marginBottom: 12, flexWrap: "wrap" },
983
+ chevronBtn: { width: 22, height: 22, display: "flex", alignItems: "center", justifyContent: "center", border: "none", background: "transparent", color: "var(--fb-muted)", flexShrink: 0 },
984
+ colorDot: { width: 10, height: 10, borderRadius: "50%", border: "1px solid var(--fb-border)", flexShrink: 0 },
985
+ sectionTitleInput: { border: "none", outline: "none", background: "transparent", fontSize: 14, fontWeight: 700, color: "var(--fb-ink)", padding: "2px 0", minWidth: 120, flex: 1 },
986
+ sectionHeaderActions: { display: "flex", alignItems: "center", gap: 6, flexWrap: "wrap", marginLeft: "auto" },
987
+ sectionEmpty: { fontSize: 12.5, color: "var(--fb-muted)", padding: "18px 4px", textAlign: "center", border: "1px dashed var(--fb-border)", borderRadius: 8 },
988
+ submitStyleRow: { display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap", background: "#F7F8FA", border: "1px solid #F0F1F4", borderRadius: 8, padding: "6px 10px", marginBottom: 12 },
989
+ miniLabel: { fontSize: 11, fontWeight: 600, color: "var(--fb-muted)" },
990
+ resetLinkBtn: { display: "flex", alignItems: "center", gap: 3, fontSize: 11, color: "var(--fb-muted)", background: "transparent", border: "none", padding: "2px 4px" },
991
+ addSectionBtn: { display: "flex", alignItems: "center", justifyContent: "center", gap: 6, width: "100%", padding: "10px", borderRadius: 10, border: "1px dashed #C7C9D6", background: "transparent", color: "var(--fb-primary)", fontSize: 13, fontWeight: 600 },
992
+ swatchRow: { display: "flex", alignItems: "center", gap: 5, flexWrap: "wrap" },
993
+ swatchBtn: { width: 20, height: 20, borderRadius: "50%", border: "1px solid var(--fb-border)", padding: 0 },
994
+ swatchBtnActive: { border: "2px solid var(--fb-primary)" },
995
+ colorPickerInput: { width: 22, height: 22, padding: 0, border: "none", borderRadius: "50%", overflow: "hidden", background: "none", cursor: "pointer" },
996
+ fieldList: { display: "flex", flexWrap: "wrap", gap: "var(--fb-space-field)" },
997
+ ticket: { display: "flex", alignItems: "stretch", background: "var(--fb-surface)", border: "1px solid var(--fb-border)", borderRadius: 10, position: "relative", transition: "border-color 0.12s, box-shadow 0.12s" },
998
+ ticketSelected: { borderColor: "var(--fb-primary)", boxShadow: "0 0 0 3px var(--fb-primary-soft)" },
999
+ ticketDragOver: { borderColor: "var(--fb-muted)" },
1000
+ ticketIndex: { fontFamily: "'JetBrains Mono', monospace", fontSize: 13, fontWeight: 700, color: "#C7C9D6", width: 34, display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0 },
1001
+ ticketPerforation: { width: 0, borderLeft: "1px dashed var(--fb-border)" },
1002
+ ticketBody: { flex: 1, padding: "10px var(--fb-space-ticket)", minWidth: 0 },
1003
+ ticketTop: { display: "flex", alignItems: "center", gap: 7, marginBottom: 4 },
1004
+ typeBadge: { fontSize: 10.5, fontFamily: "'JetBrains Mono', monospace", color: "var(--fb-muted)" },
1005
+ requiredBadge: { fontSize: 10, fontWeight: 700, color: "#B4522F", background: "var(--fb-danger-soft)", padding: "2px 6px", borderRadius: 5, letterSpacing: "0.02em", marginLeft: "auto" },
1006
+ chipRow: { display: "flex", gap: 5, marginBottom: 7, flexWrap: "wrap" },
1007
+ miniBadge: { fontSize: 10, fontWeight: 600, color: "var(--fb-primary)", background: "var(--fb-primary-soft)", padding: "2px 6px", borderRadius: 5, letterSpacing: "0.02em" },
1008
+ segmented: { display: "flex", gap: 3, background: "#F0F1F4", padding: 3, borderRadius: 8, marginBottom: 4 },
1009
+ segmentedBtn: { flex: 1, display: "flex", alignItems: "center", justifyContent: "center", gap: 5, padding: "6px 8px", borderRadius: 6, border: "none", background: "transparent", color: "#6B6E79", fontSize: 12.5, fontWeight: 500 },
1010
+ segmentedBtnActive: { flex: 1, display: "flex", alignItems: "center", justifyContent: "center", gap: 5, padding: "6px 8px", borderRadius: 6, border: "none", background: "#fff", color: "var(--fb-ink)", fontSize: 12.5, fontWeight: 600, boxShadow: "0 1px 2px rgba(0,0,0,0.08)" },
1011
+ iconGrid: { display: "grid", gridTemplateColumns: "repeat(6, 1fr)", gap: 5, marginTop: 6 },
1012
+ iconGridBtn: { width: 30, height: 30, borderRadius: 7, border: "1px solid var(--fb-border)", background: "#fff", display: "flex", alignItems: "center", justifyContent: "center", color: "#6B6E79" },
1013
+ iconGridBtnActive: { width: 30, height: 30, borderRadius: 7, border: "1px solid var(--fb-primary)", background: "var(--fb-primary-soft)", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--fb-primary)" },
1014
+ styleToggleRow: { display: "flex", gap: 5, marginBottom: 4 },
1015
+ styleToggle: { width: 30, height: 30, borderRadius: 7, border: "1px solid var(--fb-border)", background: "#fff", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--fb-muted)" },
1016
+ styleToggleActive: { width: 30, height: 30, borderRadius: 7, border: "1px solid var(--fb-primary)", background: "var(--fb-primary-soft)", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--fb-primary)" },
1017
+ previewInput: { border: "1px solid #E9EAEE", borderRadius: 7, padding: "7px 10px", fontSize: 13, color: "var(--fb-muted)", background: "#FBFBFD", display: "flex", alignItems: "center", justifyContent: "space-between" },
1018
+ previewRadio: { display: "flex", alignItems: "center", gap: 6, fontSize: 13, color: "#4A4D57" },
1019
+ ticketActions: { display: "flex", flexDirection: "column", gap: 3, padding: 8, borderLeft: "1px solid #F0F1F4" },
1020
+ iconBtn: { width: 24, height: 24, borderRadius: 6, border: "1px solid #E9EAEE", background: "#fff", display: "flex", alignItems: "center", justifyContent: "center", color: "#6B6E79" },
1021
+ iconBtnDanger: { color: "var(--fb-danger)" },
1022
+ inspector: { width: 270, borderLeft: "1px solid var(--fb-border)", background: "var(--fb-surface)", padding: "var(--fb-space-panel)", overflowY: "auto" },
1023
+ inspectorEmpty: { fontSize: 13, color: "var(--fb-muted)", lineHeight: 1.5, marginTop: 6 },
1024
+ inspectorBody: { display: "flex", flexDirection: "column", gap: 4 },
1025
+ helperText: { fontSize: 11, color: "var(--fb-muted)", lineHeight: 1.4, margin: "2px 0 4px" },
1026
+ propLabel: { fontSize: 11.5, fontWeight: 600, color: "#6B6E79", marginTop: 12, marginBottom: 5 },
1027
+ propInput: { border: "1px solid var(--fb-border)", borderRadius: 7, padding: "7px 9px", fontSize: 13, color: "var(--fb-ink)", outline: "none", width: "100%", boxSizing: "border-box" },
1028
+ optionList: { display: "flex", flexDirection: "column", gap: 6 },
1029
+ optionRow: { display: "flex", gap: 6, alignItems: "center" },
1030
+ optionInput: { flex: 1, border: "1px solid var(--fb-border)", borderRadius: 6, padding: "6px 8px", fontSize: 12.5, outline: "none", minWidth: 0, boxSizing: "border-box" },
1031
+ addOptionBtn: { display: "flex", alignItems: "center", gap: 5, fontSize: 12.5, color: "var(--fb-primary)", background: "transparent", border: "1px dashed #C7C9D6", borderRadius: 7, padding: "6px 8px", justifyContent: "center", marginTop: 2 },
1032
+ toggleRow: { display: "flex", alignItems: "center", gap: 8, background: "transparent", border: "none", padding: "8px 0" },
1033
+ toggleTrack: { width: 30, height: 17, borderRadius: 20, position: "relative", transition: "background 0.15s", display: "inline-block" },
1034
+ toggleThumb: { position: "absolute", top: 2, left: 2, width: 13, height: 13, borderRadius: "50%", background: "#fff", transition: "transform 0.15s", boxShadow: "0 1px 2px rgba(0,0,0,0.2)" },
1035
+ inspectorDivider: { height: 1, background: "var(--fb-border)", margin: "14px 0 10px" },
1036
+ deleteFieldBtn: { display: "flex", alignItems: "center", gap: 6, fontSize: 12.5, color: "var(--fb-danger)", background: "var(--fb-danger-soft)", border: "none", borderRadius: 7, padding: "8px 10px", justifyContent: "center" },
1037
+ modalOverlay: { position: "fixed", inset: 0, background: "rgba(20,22,28,0.45)", display: "flex", alignItems: "center", justifyContent: "center", zIndex: 50 },
1038
+ modal: { background: "#fff", borderRadius: 12, width: 520, maxHeight: "70vh", display: "flex", flexDirection: "column", overflow: "hidden", boxShadow: "0 20px 60px rgba(0,0,0,0.25)" },
1039
+ modalHeader: { display: "flex", alignItems: "center", justifyContent: "space-between", padding: "12px 16px", borderBottom: "1px solid var(--fb-border)" },
1040
+ jsonPre: { margin: 0, padding: 16, fontSize: 12, lineHeight: 1.6, overflow: "auto", background: "#14171C", color: "#D6D8E0", fontFamily: "'JetBrains Mono', monospace" },
1041
+ previewWrap: { flex: 1, display: "flex", justifyContent: "center", padding: "var(--fb-space-page) 20px", overflowY: "auto", background: "var(--fb-canvas)" },
1042
+ previewToolbar: { width: "100%", display: "flex", gap: 10, marginBottom: 16, justifyContent: "center" },
1043
+ previewCard: { width: "100%", maxWidth: 480, background: "var(--fb-page-bg)", border: "1px solid var(--fb-border)", borderRadius: 12, padding: "var(--fb-space-page)" },
1044
+ previewTitle: { fontSize: 19, fontWeight: 700, margin: "0 0 20px" },
1045
+ previewLabel: { display: "block", fontSize: 13, fontWeight: 600, color: "#33353E", marginBottom: 6 },
1046
+ previewGrid: { display: "flex", flexWrap: "wrap", gap: "var(--fb-space-field)" },
1047
+ sectionRuntimeTitle: { fontSize: 15, fontWeight: 700, margin: "0 0 12px", color: "var(--fb-ink)" },
1048
+ imagePlaceholder: { display: "flex", alignItems: "center", justifyContent: "center", border: "1px dashed var(--fb-border)" },
1049
+ fieldError: { display: "flex", alignItems: "center", gap: 5, fontSize: 12, color: "var(--fb-danger)", marginTop: 5 },
1050
+ formErrorNote: { display: "flex", alignItems: "center", gap: 6, fontSize: 12.5, color: "var(--fb-danger)", marginTop: 10, justifyContent: "center" },
1051
+ submittedList: { display: "flex", flexDirection: "column", gap: 8, marginTop: 4 },
1052
+ submittedRow: { display: "flex", justifyContent: "space-between", gap: 12, fontSize: 13, padding: "6px 0", borderBottom: "1px solid var(--fb-border)" },
1053
+ submittedLabel: { color: "var(--fb-muted)", flexShrink: 0 },
1054
+ submittedValue: { color: "var(--fb-ink)", fontWeight: 600, textAlign: "right" },
1055
+ realInput: { width: "100%", border: "1px solid var(--fb-border)", borderRadius: 8, padding: "9px 11px", fontSize: 13.5, outline: "none", color: "var(--fb-ink)", boxSizing: "border-box" },
1056
+ realInputError: { borderColor: "var(--fb-danger)" },
1057
+ submitBtn: { marginTop: 8, color: "#fff", border: "none", borderRadius: 8, fontWeight: 600, width: "100%" },
1058
+ layoutPanel: { position: "absolute", top: "calc(100% + 6px)", right: 0, width: 260, background: "#fff", border: "1px solid var(--fb-border)", borderRadius: 10, boxShadow: "0 12px 32px rgba(0,0,0,0.18)", padding: 14, zIndex: 60, maxHeight: "70vh", overflowY: "auto" },
1059
+ layoutPanelHeader: { display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 4 },
1060
+ spacingRow: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8, padding: "4px 0" },
1061
+ spacingLabel: { fontSize: 12, color: "#4A4D57" },
1062
+ spacingInput: { width: 60, border: "1px solid var(--fb-border)", borderRadius: 6, padding: "4px 6px", fontSize: 12, textAlign: "right", outline: "none" }
1063
+ };
1064
+
1065
+ // src/components/Segmented.tsx
1066
+ import { jsx, jsxs } from "react/jsx-runtime";
1067
+ function Segmented({ options, value, onChange }) {
1068
+ return /* @__PURE__ */ jsx("div", { style: styles.segmented, children: options.map((opt) => {
1069
+ const Icon = opt.icon;
1070
+ const active = value === opt.value;
1071
+ return /* @__PURE__ */ jsxs("button", { type: "button", style: active ? styles.segmentedBtnActive : styles.segmentedBtn, onClick: () => onChange(opt.value), children: [
1072
+ Icon && /* @__PURE__ */ jsx(Icon, { size: 13 }),
1073
+ opt.label
1074
+ ] }, opt.value);
1075
+ }) });
1076
+ }
1077
+
1078
+ // src/components/LayoutPopover.tsx
1079
+ import { useState as useState5 } from "react";
1080
+ import { Ruler, RotateCcw } from "lucide-react";
1081
+
1082
+ // src/constants/colors.ts
1083
+ var COLOR_SWATCHES = ["", "#1B1E24", "#5B5FEF", "#C4432E", "#0EA5A4", "#D97706"];
1084
+ var SECTION_BG_SWATCHES = ["", "#FFFFFF", "#F5F6F8", "#EEF0FF", "#FDF4E7", "#E9F7EF", "#FDECEC"];
1085
+ var BUTTON_COLOR_SWATCHES = ["", "#5B5FEF", "#0EA5A4", "#D97706", "#C4432E", "#1B1E24"];
1086
+
1087
+ // src/constants/layout.ts
1088
+ import { Monitor, Tablet, Smartphone } from "lucide-react";
1089
+ var WIDTH_OPTIONS = [
1090
+ { value: "1/3", label: "1/3" },
1091
+ { value: "1/2", label: "1/2" },
1092
+ { value: "1/1", labelKey: "full" }
1093
+ ];
1094
+ var WIDTH_PERCENT = { "1/3": "33.3333%", "1/2": "50%", "1/1": "100%" };
1095
+ var ALIGN_MAP = { top: "flex-start", middle: "center", bottom: "flex-end" };
1096
+ function effectiveWidth(width, device) {
1097
+ if (device === "mobile") return "1/1";
1098
+ if (device === "tablet" && width === "1/3") return "1/2";
1099
+ return width || "1/1";
1100
+ }
1101
+ var SPACING_FIELDS = [
1102
+ { key: "pagePadding", labelKey: "spacingPagePadding" },
1103
+ { key: "canvasPadding", labelKey: "spacingCanvasPadding" },
1104
+ { key: "sectionGap", labelKey: "spacingSectionGap" },
1105
+ { key: "fieldGap", labelKey: "spacingFieldGap" },
1106
+ { key: "toolbarPadding", labelKey: "spacingToolbarPadding" },
1107
+ { key: "panelPadding", labelKey: "spacingPanelPadding" },
1108
+ { key: "ticketPadding", labelKey: "spacingTicketPadding" }
1109
+ ];
1110
+ function buildDeviceOptions(baseMaxWidth, chrome) {
1111
+ const mw = Number(baseMaxWidth) > 0 ? baseMaxWidth : DEFAULT_THEME.layout.maxWidth;
1112
+ return [
1113
+ { value: "laptop", label: chrome.deviceLaptop, icon: Monitor, maxWidth: mw },
1114
+ { value: "tablet", label: chrome.deviceTablet, icon: Tablet, maxWidth: Math.min(mw, 480) },
1115
+ { value: "mobile", label: chrome.deviceMobile, icon: Smartphone, maxWidth: Math.min(mw, 340) }
1116
+ ];
1117
+ }
1118
+
1119
+ // src/components/LayoutPopover.tsx
1120
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1121
+ function LayoutPopover({ chrome, theme, updateThemeColor, updateThemeLayout, resetTheme }) {
1122
+ const [open, setOpen] = useState5(false);
1123
+ return /* @__PURE__ */ jsxs2("div", { style: { position: "relative" }, children: [
1124
+ /* @__PURE__ */ jsx2("button", { style: styles.iconBtn, title: chrome.spacing, onClick: () => setOpen((v) => !v), children: /* @__PURE__ */ jsx2(Ruler, { size: 14 }) }),
1125
+ open && /* @__PURE__ */ jsxs2("div", { style: styles.layoutPanel, children: [
1126
+ /* @__PURE__ */ jsxs2("div", { style: styles.layoutPanelHeader, children: [
1127
+ /* @__PURE__ */ jsxs2("span", { style: { fontWeight: 600, fontSize: 13 }, children: [
1128
+ chrome.theme,
1129
+ " / ",
1130
+ chrome.spacing
1131
+ ] }),
1132
+ /* @__PURE__ */ jsxs2("button", { type: "button", style: styles.resetLinkBtn, onClick: resetTheme, children: [
1133
+ /* @__PURE__ */ jsx2(RotateCcw, { size: 11 }),
1134
+ " ",
1135
+ chrome.reset
1136
+ ] })
1137
+ ] }),
1138
+ /* @__PURE__ */ jsx2("label", { style: styles.propLabel, children: chrome.primaryColor }),
1139
+ /* @__PURE__ */ jsxs2("div", { style: styles.swatchRow, children: [
1140
+ BUTTON_COLOR_SWATCHES.slice(1).map((c) => /* @__PURE__ */ jsx2("button", { type: "button", title: c, style: { ...styles.swatchBtn, background: c, ...theme.primary === c ? styles.swatchBtnActive : {} }, onClick: () => updateThemeColor("primary", c) }, c)),
1141
+ /* @__PURE__ */ jsx2("input", { type: "color", value: theme.primary, onChange: (e) => updateThemeColor("primary", e.target.value), style: styles.colorPickerInput })
1142
+ ] }),
1143
+ /* @__PURE__ */ jsx2("label", { style: styles.propLabel, children: chrome.pageBackground }),
1144
+ /* @__PURE__ */ jsxs2("div", { style: styles.swatchRow, children: [
1145
+ SECTION_BG_SWATCHES.filter(Boolean).map((c) => /* @__PURE__ */ jsx2("button", { type: "button", title: c, style: { ...styles.swatchBtn, background: c, ...theme.pageBackground === c ? styles.swatchBtnActive : {} }, onClick: () => updateThemeColor("pageBackground", c) }, c)),
1146
+ /* @__PURE__ */ jsx2("input", { type: "color", value: theme.pageBackground, onChange: (e) => updateThemeColor("pageBackground", e.target.value), style: styles.colorPickerInput })
1147
+ ] }),
1148
+ /* @__PURE__ */ jsx2("label", { style: styles.propLabel, children: chrome.maxWidthPx }),
1149
+ /* @__PURE__ */ jsx2("input", { type: "number", style: styles.propInput, value: theme.layout.maxWidth, onChange: (e) => updateThemeLayout("maxWidth", Number(e.target.value) || DEFAULT_THEME.layout.maxWidth) }),
1150
+ /* @__PURE__ */ jsx2("div", { style: styles.inspectorDivider }),
1151
+ /* @__PURE__ */ jsx2("div", { style: styles.panelHeading, children: chrome.spacing }),
1152
+ SPACING_FIELDS.map(({ key, labelKey }) => /* @__PURE__ */ jsxs2("div", { style: styles.spacingRow, children: [
1153
+ /* @__PURE__ */ jsx2("span", { style: styles.spacingLabel, children: chrome[labelKey] }),
1154
+ /* @__PURE__ */ jsx2("input", { type: "number", style: styles.spacingInput, value: theme.layout[key], onChange: (e) => updateThemeLayout(key, Number(e.target.value) || 0) })
1155
+ ] }, key))
1156
+ ] })
1157
+ ] });
1158
+ }
1159
+
1160
+ // src/components/Toolbar.tsx
1161
+ import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1162
+ function Toolbar({
1163
+ title,
1164
+ language,
1165
+ languages,
1166
+ mode,
1167
+ saveState,
1168
+ chrome,
1169
+ themeEditable,
1170
+ theme,
1171
+ savedFormsCount,
1172
+ onTitleChange,
1173
+ onLanguageChange,
1174
+ onModeChange,
1175
+ onOpenSettings,
1176
+ onNewForm,
1177
+ onOpenLibrary,
1178
+ onSaveExisting,
1179
+ onOpenJson,
1180
+ updateThemeColor,
1181
+ updateThemeLayout,
1182
+ resetTheme
1183
+ }) {
1184
+ return /* @__PURE__ */ jsxs3("div", { style: styles.toolbar, children: [
1185
+ /* @__PURE__ */ jsxs3("div", { style: styles.toolbarLeft, children: [
1186
+ /* @__PURE__ */ jsx3("div", { style: styles.logoMark, children: "FB" }),
1187
+ /* @__PURE__ */ jsx3("input", { value: t(title, language), onChange: (e) => onTitleChange(e.target.value), style: styles.titleInput, "aria-label": "Form title" })
1188
+ ] }),
1189
+ /* @__PURE__ */ jsxs3("div", { style: styles.toolbarRight, children: [
1190
+ /* @__PURE__ */ jsxs3("span", { style: styles.saveStatus, children: [
1191
+ saveState === "saving" && /* @__PURE__ */ jsxs3(Fragment, { children: [
1192
+ /* @__PURE__ */ jsx3(Loader2, { size: 12, className: "spin" }),
1193
+ " ",
1194
+ chrome.saving
1195
+ ] }),
1196
+ saveState === "saved" && chrome.saved,
1197
+ saveState === "error" && /* @__PURE__ */ jsxs3("span", { style: { color: "var(--fb-danger)", display: "flex", alignItems: "center", gap: 4 }, children: [
1198
+ /* @__PURE__ */ jsx3(AlertCircle, { size: 12 }),
1199
+ " ",
1200
+ chrome.saveFailed
1201
+ ] })
1202
+ ] }),
1203
+ /* @__PURE__ */ jsx3("div", { style: styles.toolbarDivider }),
1204
+ /* @__PURE__ */ jsx3(Languages, { size: 14, color: "var(--fb-muted)" }),
1205
+ /* @__PURE__ */ jsx3(Segmented, { options: languages.map((l) => ({ value: l.code, label: l.label })), value: language, onChange: onLanguageChange }),
1206
+ /* @__PURE__ */ jsx3("div", { style: styles.toolbarDivider }),
1207
+ /* @__PURE__ */ jsxs3("button", { style: mode === "build" ? styles.tabBtnActive : styles.tabBtn, onClick: () => onModeChange("build"), children: [
1208
+ /* @__PURE__ */ jsx3(Pencil, { size: 14 }),
1209
+ " ",
1210
+ chrome.build
1211
+ ] }),
1212
+ /* @__PURE__ */ jsxs3("button", { style: mode === "preview" ? styles.tabBtnActive : styles.tabBtn, onClick: () => onModeChange("preview"), children: [
1213
+ /* @__PURE__ */ jsx3(Eye, { size: 14 }),
1214
+ " ",
1215
+ chrome.preview
1216
+ ] }),
1217
+ /* @__PURE__ */ jsx3("div", { style: styles.toolbarDivider }),
1218
+ themeEditable && /* @__PURE__ */ jsx3(LayoutPopover, { chrome, theme, updateThemeColor, updateThemeLayout, resetTheme }),
1219
+ /* @__PURE__ */ jsx3("button", { style: styles.iconBtn, title: chrome.settings, onClick: onOpenSettings, children: /* @__PURE__ */ jsx3(Settings, { size: 14 }) }),
1220
+ /* @__PURE__ */ jsxs3("button", { style: styles.ghostBtn, onClick: onNewForm, title: chrome.startNewForm, children: [
1221
+ /* @__PURE__ */ jsx3(FilePlus2, { size: 14 }),
1222
+ " ",
1223
+ chrome.newForm
1224
+ ] }),
1225
+ /* @__PURE__ */ jsxs3("button", { style: styles.ghostBtn, onClick: onOpenLibrary, title: chrome.openSavedForm, children: [
1226
+ /* @__PURE__ */ jsx3(FolderOpen, { size: 14 }),
1227
+ " ",
1228
+ chrome.myForms,
1229
+ savedFormsCount > 0 && /* @__PURE__ */ jsx3("span", { style: styles.countBadge, children: savedFormsCount })
1230
+ ] }),
1231
+ /* @__PURE__ */ jsxs3("button", { style: styles.primaryBtn, onClick: onSaveExisting, title: chrome.saveToLibraryTitle, children: [
1232
+ /* @__PURE__ */ jsx3(Save, { size: 14 }),
1233
+ " ",
1234
+ chrome.save
1235
+ ] }),
1236
+ /* @__PURE__ */ jsxs3("button", { style: styles.ghostBtn, onClick: onOpenJson, children: [
1237
+ /* @__PURE__ */ jsx3(Code2, { size: 14 }),
1238
+ " ",
1239
+ chrome.viewJson
1240
+ ] })
1241
+ ] })
1242
+ ] });
1243
+ }
1244
+
1245
+ // src/components/Palette.tsx
1246
+ import { Plus } from "lucide-react";
1247
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1248
+ function Palette({ activeSectionLabel, chrome, onAddField }) {
1249
+ return /* @__PURE__ */ jsxs4("div", { style: styles.palette, children: [
1250
+ /* @__PURE__ */ jsxs4("div", { style: styles.activeSectionHint, children: [
1251
+ chrome.addingTo,
1252
+ " ",
1253
+ /* @__PURE__ */ jsx4("strong", { children: activeSectionLabel })
1254
+ ] }),
1255
+ /* @__PURE__ */ jsx4("div", { style: styles.panelHeading, children: chrome.contentBlocks }),
1256
+ /* @__PURE__ */ jsx4("div", { style: styles.paletteList, children: CONTENT_TYPES.map((f) => {
1257
+ const Icon = f.icon;
1258
+ return /* @__PURE__ */ jsxs4("button", { style: styles.paletteItem, onClick: () => onAddField(f.type), children: [
1259
+ /* @__PURE__ */ jsx4(Icon, { size: 16, color: "var(--fb-primary)" }),
1260
+ /* @__PURE__ */ jsx4("span", { children: chrome[FIELD_TYPE_CHROME_KEY[f.type]] }),
1261
+ /* @__PURE__ */ jsx4(Plus, { size: 13, color: "#A6A8B3", style: { marginLeft: "auto" } })
1262
+ ] }, f.type);
1263
+ }) }),
1264
+ /* @__PURE__ */ jsx4("div", { style: { ...styles.panelHeading, marginTop: 16 }, children: chrome.formFields }),
1265
+ /* @__PURE__ */ jsx4("div", { style: styles.paletteList, children: FORM_TYPES.map((f) => {
1266
+ const Icon = f.icon;
1267
+ return /* @__PURE__ */ jsxs4("button", { style: styles.paletteItem, onClick: () => onAddField(f.type), children: [
1268
+ /* @__PURE__ */ jsx4(Icon, { size: 16, color: "var(--fb-primary)" }),
1269
+ /* @__PURE__ */ jsx4("span", { children: chrome[FIELD_TYPE_CHROME_KEY[f.type]] }),
1270
+ /* @__PURE__ */ jsx4(Plus, { size: 13, color: "#A6A8B3", style: { marginLeft: "auto" } })
1271
+ ] }, f.type);
1272
+ }) })
1273
+ ] });
1274
+ }
1275
+
1276
+ // src/components/Canvas.tsx
1277
+ import { Layers } from "lucide-react";
1278
+
1279
+ // src/components/SectionCard.tsx
1280
+ import { ChevronDown as ChevronDown3, ChevronRight, Copy as Copy2, ArrowUp as ArrowUp2, ArrowDown as ArrowDown2, Trash2 as Trash22, RotateCcw as RotateCcw2 } from "lucide-react";
1281
+
1282
+ // src/constants/submitStyle.ts
1283
+ var SUBMIT_SIZE_OPTIONS = [
1284
+ { value: "sm", label: "S" },
1285
+ { value: "md", label: "M" },
1286
+ { value: "lg", label: "L" }
1287
+ ];
1288
+ var SUBMIT_SIZE_STYLES = {
1289
+ sm: { padding: "7px 12px", fontSize: 12.5 },
1290
+ md: { padding: "10px 16px", fontSize: 13.5 },
1291
+ lg: { padding: "13px 22px", fontSize: 15 }
1292
+ };
1293
+ function resolveSubmitStyle(style) {
1294
+ const sizeStyle = SUBMIT_SIZE_STYLES[style?.size || "md"] || SUBMIT_SIZE_STYLES.md;
1295
+ return { ...sizeStyle, background: style?.color || "var(--fb-primary)" };
1296
+ }
1297
+
1298
+ // src/components/FieldTicket.tsx
1299
+ import { ArrowUp, ArrowDown, Copy, Trash2, GripVertical } from "lucide-react";
1300
+
1301
+ // src/components/fields/FieldBlock.tsx
1302
+ import { CircleAlert } from "lucide-react";
1303
+
1304
+ // src/constants/typography.ts
1305
+ import { AlignLeft as AlignLeft2, AlignCenter, AlignRight } from "lucide-react";
1306
+ var FONT_SIZE_OPTIONS = [
1307
+ { value: "sm", label: "S", px: 12 },
1308
+ { value: "md", label: "M", px: 14 },
1309
+ { value: "lg", label: "L", px: 18 },
1310
+ { value: "xl", label: "XL", px: 24 }
1311
+ ];
1312
+ var TEXT_ALIGN_OPTIONS = [
1313
+ { value: "left", label: "", icon: AlignLeft2 },
1314
+ { value: "center", label: "", icon: AlignCenter },
1315
+ { value: "right", label: "", icon: AlignRight }
1316
+ ];
1317
+ var PARAGRAPH_TAG_OPTIONS = ["h1", "h2", "h3", "p", "caption"];
1318
+ var TAG_PRESETS = {
1319
+ h1: { fontSize: "xl", fontWeight: "bold" },
1320
+ h2: { fontSize: "lg", fontWeight: "bold" },
1321
+ h3: { fontSize: "md", fontWeight: "bold" },
1322
+ p: { fontSize: "md", fontWeight: "normal" },
1323
+ caption: { fontSize: "sm", fontWeight: "normal" }
1324
+ };
1325
+ var TAG_TO_ELEMENT = { h1: "h1", h2: "h2", h3: "h3", p: "p", caption: "span" };
1326
+ var TAG_CHROME_KEY = { p: "tagBody", caption: "tagCaption" };
1327
+
1328
+ // src/constants/icons.ts
1329
+ import {
1330
+ Type as Type2,
1331
+ Mail,
1332
+ Phone,
1333
+ Hash,
1334
+ Lock,
1335
+ AlignLeft as AlignLeft3,
1336
+ ChevronDown as ChevronDown2,
1337
+ Circle as Circle2,
1338
+ CheckSquare as CheckSquare2,
1339
+ ListChecks as ListChecks2,
1340
+ ToggleLeft as ToggleLeft2,
1341
+ Calendar,
1342
+ Clock,
1343
+ User,
1344
+ MapPin,
1345
+ Building2,
1346
+ Globe,
1347
+ CreditCard,
1348
+ FileText as FileText2,
1349
+ Star,
1350
+ Flag,
1351
+ Home,
1352
+ Briefcase
1353
+ } from "lucide-react";
1354
+ var ICON_LIBRARY = {
1355
+ Type: Type2,
1356
+ Mail,
1357
+ Phone,
1358
+ Hash,
1359
+ Lock,
1360
+ AlignLeft: AlignLeft3,
1361
+ ChevronDown: ChevronDown2,
1362
+ Circle: Circle2,
1363
+ CheckSquare: CheckSquare2,
1364
+ ListChecks: ListChecks2,
1365
+ ToggleLeft: ToggleLeft2,
1366
+ Calendar,
1367
+ Clock,
1368
+ User,
1369
+ MapPin,
1370
+ Building2,
1371
+ Globe,
1372
+ CreditCard,
1373
+ FileText: FileText2,
1374
+ Star,
1375
+ Flag,
1376
+ Home,
1377
+ Briefcase
1378
+ };
1379
+
1380
+ // src/components/fields/ImageBlock.tsx
1381
+ import { Image as ImageIcon2 } from "lucide-react";
1382
+ import { jsx as jsx5 } from "react/jsx-runtime";
1383
+ function buildImageStyle(field) {
1384
+ const aspectRatio = field.shape === "banner" ? "16 / 5" : "1 / 1";
1385
+ return {
1386
+ width: "100%",
1387
+ aspectRatio,
1388
+ objectFit: "cover",
1389
+ display: "block",
1390
+ borderRadius: field.shape === "circle" ? "50%" : 8,
1391
+ background: "var(--fb-canvas)"
1392
+ };
1393
+ }
1394
+ function ImageBlock({ field, lang }) {
1395
+ const shapeStyle = buildImageStyle(field);
1396
+ const img = field.src ? /* @__PURE__ */ jsx5("img", { src: field.src, alt: t(field.alt, lang || "en"), style: shapeStyle }) : /* @__PURE__ */ jsx5("div", { style: { ...shapeStyle, ...styles.imagePlaceholder }, children: /* @__PURE__ */ jsx5(ImageIcon2, { size: 20, color: "var(--fb-muted)" }) });
1397
+ return field.link ? /* @__PURE__ */ jsx5("a", { href: field.link, target: "_blank", rel: "noreferrer", style: { display: "block" }, children: img }) : img;
1398
+ }
1399
+
1400
+ // src/components/fields/FieldBlock.tsx
1401
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1402
+ function FieldBlock({ field, lang, onFieldChange, strings, chrome, error, isBuild }) {
1403
+ if (field.type === "image") return /* @__PURE__ */ jsx6(ImageBlock, { field, lang });
1404
+ if (field.type === "paragraph") {
1405
+ const fontPx = FONT_SIZE_OPTIONS.find((f) => f.value === field.fontSize)?.px || 14;
1406
+ const Tag = TAG_TO_ELEMENT[field.tag] || "p";
1407
+ return /* @__PURE__ */ jsx6(Tag, { style: { margin: 0, whiteSpace: "pre-wrap", fontSize: fontPx, fontWeight: field.fontWeight === "bold" ? 700 : 400, fontStyle: field.fontStyle === "italic" ? "italic" : "normal", textAlign: field.textAlign || "left", color: field.color || "var(--fb-ink)", lineHeight: 1.6 }, children: t(field.content, lang) || "..." });
1408
+ }
1409
+ const meta = getMeta(field.type);
1410
+ const IconComp = field.showIcon && field.displayIcon ? ICON_LIBRARY[field.displayIcon] : null;
1411
+ const isInline = field.labelPosition === "inline" && !meta.boolean;
1412
+ const labelText = t(field.label, lang);
1413
+ const showLabel = !meta.boolean && !field.hideLabel;
1414
+ return /* @__PURE__ */ jsxs5("div", { children: [
1415
+ /* @__PURE__ */ jsxs5("div", { style: { display: "flex", flexDirection: isInline ? "row" : "column", alignItems: isInline ? "center" : "stretch", gap: isInline ? 10 : 6 }, children: [
1416
+ showLabel && /* @__PURE__ */ jsxs5("label", { style: { ...styles.previewLabel, marginBottom: isInline ? 0 : 6, flexShrink: 0, width: isInline ? 120 : "auto", display: "flex", alignItems: "center", gap: 6 }, children: [
1417
+ IconComp && /* @__PURE__ */ jsx6(IconComp, { size: 14, color: "#6B6E79" }),
1418
+ labelText || (isBuild ? /* @__PURE__ */ jsx6("span", { style: { fontStyle: "italic", color: "var(--fb-muted)" }, children: chrome?.untitledField || "Untitled field" }) : ""),
1419
+ field.required && /* @__PURE__ */ jsx6("span", { style: { color: "var(--fb-danger)" }, children: " *" })
1420
+ ] }),
1421
+ /* @__PURE__ */ jsx6("div", { style: { flex: 1, minWidth: 0 }, children: renderInteractive(field, lang, onFieldChange, IconComp, !!error, strings) })
1422
+ ] }),
1423
+ error && /* @__PURE__ */ jsxs5("div", { style: styles.fieldError, children: [
1424
+ /* @__PURE__ */ jsx6(CircleAlert, { size: 12 }),
1425
+ " ",
1426
+ error
1427
+ ] })
1428
+ ] });
1429
+ }
1430
+ function renderInteractive(field, lang, onFieldChange, IconComp, hasError, strings) {
1431
+ const value = field.defaultValue;
1432
+ const set = (v) => onFieldChange(field.id, { defaultValue: v });
1433
+ const inputStyle = hasError ? { ...styles.realInput, ...styles.realInputError } : styles.realInput;
1434
+ const common = { style: inputStyle, placeholder: t("placeholder" in field ? field.placeholder : void 0, lang), value: value || "", onChange: (e) => set(e.target.value) };
1435
+ if (field.type === "input") {
1436
+ switch (field.inputType) {
1437
+ case "number":
1438
+ return /* @__PURE__ */ jsx6("input", { type: "number", ...common, min: field.min ?? void 0, max: field.max ?? void 0 });
1439
+ case "email":
1440
+ return /* @__PURE__ */ jsx6("input", { type: "email", ...common });
1441
+ case "password":
1442
+ return /* @__PURE__ */ jsx6("input", { type: "password", ...common, maxLength: field.maxLength || void 0 });
1443
+ case "phone":
1444
+ return /* @__PURE__ */ jsx6("input", { type: "tel", ...common });
1445
+ case "date":
1446
+ return /* @__PURE__ */ jsx6("input", { type: "date", ...common });
1447
+ case "time":
1448
+ return /* @__PURE__ */ jsx6("input", { type: "time", ...common });
1449
+ default:
1450
+ return /* @__PURE__ */ jsx6("input", { type: "text", ...common, maxLength: field.maxLength || void 0 });
1451
+ }
1452
+ }
1453
+ switch (field.type) {
1454
+ case "textarea":
1455
+ return /* @__PURE__ */ jsx6("textarea", { ...common, rows: 3, maxLength: field.maxLength || void 0 });
1456
+ case "select":
1457
+ return /* @__PURE__ */ jsxs5("select", { style: inputStyle, value: value || "", onChange: (e) => set(e.target.value), children: [
1458
+ /* @__PURE__ */ jsx6("option", { value: "", children: strings.selectPlaceholder }),
1459
+ (field.options || []).map((o) => /* @__PURE__ */ jsx6("option", { value: o.value, children: t(o.label, lang) }, o.value))
1460
+ ] });
1461
+ case "radio":
1462
+ return /* @__PURE__ */ jsx6("div", { style: { display: "flex", gap: 16, flexWrap: "wrap", marginTop: 4 }, children: (field.options || []).map((o) => /* @__PURE__ */ jsxs5("label", { style: styles.previewRadio, children: [
1463
+ /* @__PURE__ */ jsx6("input", { type: "radio", name: field.id, value: o.value, checked: value === o.value, onChange: (e) => set(e.target.value) }),
1464
+ t(o.label, lang)
1465
+ ] }, o.value)) });
1466
+ case "checkboxGroup": {
1467
+ const vals = Array.isArray(value) ? value : [];
1468
+ return /* @__PURE__ */ jsx6("div", { style: { display: "flex", gap: 16, flexWrap: "wrap", marginTop: 4 }, children: (field.options || []).map((o) => {
1469
+ const checked = vals.includes(o.value);
1470
+ return /* @__PURE__ */ jsxs5("label", { style: styles.previewRadio, children: [
1471
+ /* @__PURE__ */ jsx6("input", { type: "checkbox", checked, onChange: (e) => {
1472
+ const next = e.target.checked ? [...vals, o.value] : vals.filter((v) => v !== o.value);
1473
+ set(next);
1474
+ }, style: { accentColor: "var(--fb-primary)" } }),
1475
+ t(o.label, lang)
1476
+ ] }, o.value);
1477
+ }) });
1478
+ }
1479
+ case "checkbox":
1480
+ return /* @__PURE__ */ jsxs5("label", { style: styles.previewRadio, children: [
1481
+ /* @__PURE__ */ jsx6("input", { type: "checkbox", checked: !!value, onChange: (e) => set(e.target.checked), style: { accentColor: "var(--fb-primary)" } }),
1482
+ IconComp && /* @__PURE__ */ jsx6(IconComp, { size: 14, color: "#6B6E79" }),
1483
+ t(field.label, lang),
1484
+ field.required && /* @__PURE__ */ jsx6("span", { style: { color: "var(--fb-danger)" }, children: " *" })
1485
+ ] });
1486
+ case "toggle":
1487
+ return /* @__PURE__ */ jsxs5("label", { style: styles.previewRadio, children: [
1488
+ /* @__PURE__ */ jsx6("input", { type: "checkbox", checked: !!value, onChange: (e) => set(e.target.checked), style: { accentColor: "var(--fb-primary)" } }),
1489
+ IconComp && /* @__PURE__ */ jsx6(IconComp, { size: 14, color: "#6B6E79" }),
1490
+ t(field.label, lang)
1491
+ ] });
1492
+ default:
1493
+ return /* @__PURE__ */ jsx6("input", { type: "text", ...common });
1494
+ }
1495
+ }
1496
+
1497
+ // src/components/FieldTicket.tsx
1498
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1499
+ function FieldTicket({
1500
+ field,
1501
+ idx,
1502
+ isSelected,
1503
+ isDragOver,
1504
+ chrome,
1505
+ strings,
1506
+ language,
1507
+ onSelect,
1508
+ onFieldChange,
1509
+ onMoveField,
1510
+ onDuplicateField,
1511
+ onDeleteField,
1512
+ dropZoneHandlers,
1513
+ dragHandleProps
1514
+ }) {
1515
+ const meta = getMeta(field.type);
1516
+ const Icon = meta.icon;
1517
+ const widthPct = WIDTH_PERCENT[field.width || "1/1"];
1518
+ return /* @__PURE__ */ jsxs6(
1519
+ "div",
1520
+ {
1521
+ ...dropZoneHandlers,
1522
+ onClick: onSelect,
1523
+ style: {
1524
+ ...styles.ticket,
1525
+ flex: `1 1 calc(${widthPct} - 10px)`,
1526
+ minWidth: 230,
1527
+ alignSelf: ALIGN_MAP[field.verticalAlign || "top"],
1528
+ ...isSelected ? styles.ticketSelected : {},
1529
+ ...isDragOver ? styles.ticketDragOver : {}
1530
+ },
1531
+ children: [
1532
+ /* @__PURE__ */ jsx7("div", { style: styles.ticketIndex, children: String(idx + 1).padStart(2, "0") }),
1533
+ /* @__PURE__ */ jsx7("div", { style: styles.ticketPerforation }),
1534
+ /* @__PURE__ */ jsxs6("div", { style: styles.ticketBody, children: [
1535
+ /* @__PURE__ */ jsxs6("div", { style: styles.ticketTop, children: [
1536
+ /* @__PURE__ */ jsx7("span", { ...dragHandleProps, style: { cursor: "grab", display: "flex" }, children: /* @__PURE__ */ jsx7(GripVertical, { size: 14, color: "#C4C6D0" }) }),
1537
+ /* @__PURE__ */ jsx7(Icon, { size: 13, color: "var(--fb-primary)" }),
1538
+ /* @__PURE__ */ jsxs6("span", { style: styles.typeBadge, children: [
1539
+ chrome[FIELD_TYPE_CHROME_KEY[field.type]] || field.type,
1540
+ field.type === "input" && field.inputType && field.inputType !== "text" ? ` \xB7 ${chrome[INPUT_SUBTYPE_CHROME_KEY[field.inputType]]}` : ""
1541
+ ] }),
1542
+ field.required && /* @__PURE__ */ jsx7("span", { style: styles.requiredBadge, children: chrome.required })
1543
+ ] }),
1544
+ field.width && field.width !== "1/1" || field.labelPosition === "inline" || field.verticalAlign !== "top" || field.hideLabel ? /* @__PURE__ */ jsxs6("div", { style: styles.chipRow, children: [
1545
+ field.width && field.width !== "1/1" && /* @__PURE__ */ jsxs6("span", { style: styles.miniBadge, children: [
1546
+ chrome.width,
1547
+ " ",
1548
+ field.width
1549
+ ] }),
1550
+ field.labelPosition === "inline" && /* @__PURE__ */ jsx7("span", { style: styles.miniBadge, children: chrome.inline }),
1551
+ field.verticalAlign !== "top" && /* @__PURE__ */ jsx7("span", { style: styles.miniBadge, children: chrome[field.verticalAlign] }),
1552
+ field.hideLabel && /* @__PURE__ */ jsx7("span", { style: styles.miniBadge, children: chrome.hideLabel })
1553
+ ] }) : null,
1554
+ /* @__PURE__ */ jsx7("div", { style: { marginTop: 8 }, children: /* @__PURE__ */ jsx7(FieldBlock, { field, lang: language, onFieldChange, strings, chrome, isBuild: true }) })
1555
+ ] }),
1556
+ /* @__PURE__ */ jsxs6("div", { style: styles.ticketActions, onClick: (e) => e.stopPropagation(), children: [
1557
+ /* @__PURE__ */ jsx7("button", { style: styles.iconBtn, title: chrome.moveUp, onClick: () => onMoveField(field.id, -1), children: /* @__PURE__ */ jsx7(ArrowUp, { size: 13 }) }),
1558
+ /* @__PURE__ */ jsx7("button", { style: styles.iconBtn, title: chrome.moveDown, onClick: () => onMoveField(field.id, 1), children: /* @__PURE__ */ jsx7(ArrowDown, { size: 13 }) }),
1559
+ /* @__PURE__ */ jsx7("button", { style: styles.iconBtn, title: chrome.duplicate, onClick: () => onDuplicateField(field.id), children: /* @__PURE__ */ jsx7(Copy, { size: 13 }) }),
1560
+ /* @__PURE__ */ jsx7("button", { style: { ...styles.iconBtn, ...styles.iconBtnDanger }, title: chrome.delete, onClick: () => onDeleteField(field.id), children: /* @__PURE__ */ jsx7(Trash2, { size: 13 }) })
1561
+ ] })
1562
+ ]
1563
+ }
1564
+ );
1565
+ }
1566
+
1567
+ // src/components/SectionCard.tsx
1568
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1569
+ function SectionCard({
1570
+ section,
1571
+ sIdx,
1572
+ sectionsLength,
1573
+ isActive,
1574
+ submitMode,
1575
+ submitStyle,
1576
+ selectedId,
1577
+ dragOverKey,
1578
+ chrome,
1579
+ strings,
1580
+ language,
1581
+ onActivate,
1582
+ onToggleCollapse,
1583
+ onUpdateTitle,
1584
+ onUpdateBackground,
1585
+ onDuplicateSection,
1586
+ onMoveSection,
1587
+ onDeleteSection,
1588
+ onUpdateSectionSubmitStyle,
1589
+ onClearSectionSubmitStyle,
1590
+ onSelectField,
1591
+ onFieldChange,
1592
+ onMoveField,
1593
+ onDuplicateField,
1594
+ onDeleteField,
1595
+ getDropZoneHandlers,
1596
+ getDragHandleProps
1597
+ }) {
1598
+ return /* @__PURE__ */ jsxs7(
1599
+ "div",
1600
+ {
1601
+ style: { ...styles.sectionBlock, background: section.background || "transparent", border: isActive ? "1px solid var(--fb-primary)" : "1px dashed var(--fb-border)" },
1602
+ onClick: onActivate,
1603
+ children: [
1604
+ /* @__PURE__ */ jsxs7("div", { style: styles.sectionHeader, children: [
1605
+ /* @__PURE__ */ jsx8("button", { style: styles.chevronBtn, onClick: (e) => {
1606
+ e.stopPropagation();
1607
+ onToggleCollapse();
1608
+ }, children: section.collapsed ? /* @__PURE__ */ jsx8(ChevronRight, { size: 15 }) : /* @__PURE__ */ jsx8(ChevronDown3, { size: 15 }) }),
1609
+ /* @__PURE__ */ jsx8("span", { style: { ...styles.colorDot, background: section.background || "var(--fb-canvas)" } }),
1610
+ /* @__PURE__ */ jsx8(
1611
+ "input",
1612
+ {
1613
+ style: styles.sectionTitleInput,
1614
+ placeholder: chrome.sectionTitlePlaceholder(sIdx + 1),
1615
+ value: t(section.title, language),
1616
+ onChange: (e) => onUpdateTitle(e.target.value),
1617
+ onClick: (e) => e.stopPropagation()
1618
+ }
1619
+ ),
1620
+ section.collapsed && /* @__PURE__ */ jsx8("span", { style: styles.miniBadge, children: chrome.fieldsCount(section.fields.length) }),
1621
+ /* @__PURE__ */ jsxs7("div", { style: styles.sectionHeaderActions, onClick: (e) => e.stopPropagation(), children: [
1622
+ !section.collapsed && /* @__PURE__ */ jsxs7("div", { style: styles.swatchRow, children: [
1623
+ SECTION_BG_SWATCHES.map((c) => /* @__PURE__ */ jsx8("button", { type: "button", title: c || chrome.none, style: { ...styles.swatchBtn, background: c || "var(--fb-canvas)", ...section.background === c ? styles.swatchBtnActive : {} }, onClick: () => onUpdateBackground(c) }, c || "none")),
1624
+ /* @__PURE__ */ jsx8("input", { type: "color", title: chrome.customColor, value: /^#/.test(section.background) ? section.background : "#ffffff", onChange: (e) => onUpdateBackground(e.target.value), style: styles.colorPickerInput })
1625
+ ] }),
1626
+ /* @__PURE__ */ jsx8("button", { style: styles.iconBtn, title: chrome.duplicate, onClick: onDuplicateSection, children: /* @__PURE__ */ jsx8(Copy2, { size: 13 }) }),
1627
+ /* @__PURE__ */ jsx8("button", { style: styles.iconBtn, title: chrome.moveUp, disabled: sIdx === 0, onClick: () => onMoveSection(-1), children: /* @__PURE__ */ jsx8(ArrowUp2, { size: 13 }) }),
1628
+ /* @__PURE__ */ jsx8("button", { style: styles.iconBtn, title: chrome.moveDown, disabled: sIdx === sectionsLength - 1, onClick: () => onMoveSection(1), children: /* @__PURE__ */ jsx8(ArrowDown2, { size: 13 }) }),
1629
+ /* @__PURE__ */ jsx8("button", { style: { ...styles.iconBtn, ...styles.iconBtnDanger }, title: chrome.deleteSection, disabled: sectionsLength <= 1, onClick: onDeleteSection, children: /* @__PURE__ */ jsx8(Trash22, { size: 13 }) })
1630
+ ] })
1631
+ ] }),
1632
+ !section.collapsed && submitMode === "perSection" && /* @__PURE__ */ jsxs7("div", { style: styles.submitStyleRow, onClick: (e) => e.stopPropagation(), children: [
1633
+ /* @__PURE__ */ jsx8("span", { style: styles.miniLabel, children: chrome.submitStyle }),
1634
+ /* @__PURE__ */ jsx8("div", { style: styles.swatchRow, children: BUTTON_COLOR_SWATCHES.map((c) => /* @__PURE__ */ jsx8("button", { type: "button", title: c || chrome.none, style: { ...styles.swatchBtn, background: c || "var(--fb-primary)", ...(section.submitStyle?.color || "") === c ? styles.swatchBtnActive : {} }, onClick: () => onUpdateSectionSubmitStyle({ color: c }) }, c || "inherit")) }),
1635
+ /* @__PURE__ */ jsx8(Segmented, { options: SUBMIT_SIZE_OPTIONS, value: section.submitStyle?.size || submitStyle.size, onChange: (v) => onUpdateSectionSubmitStyle({ size: v }) }),
1636
+ section.submitStyle && /* @__PURE__ */ jsxs7("button", { type: "button", style: styles.resetLinkBtn, onClick: onClearSectionSubmitStyle, children: [
1637
+ /* @__PURE__ */ jsx8(RotateCcw2, { size: 11 }),
1638
+ " ",
1639
+ chrome.reset
1640
+ ] })
1641
+ ] }),
1642
+ !section.collapsed && (section.fields.length === 0 ? /* @__PURE__ */ jsx8("div", { style: styles.sectionEmpty, children: chrome.noFieldsInSection }) : /* @__PURE__ */ jsx8("div", { style: styles.fieldList, children: section.fields.map((field, idx) => /* @__PURE__ */ jsx8(
1643
+ FieldTicket,
1644
+ {
1645
+ field,
1646
+ idx,
1647
+ sectionId: section.id,
1648
+ isSelected: field.id === selectedId,
1649
+ isDragOver: dragOverKey === `${section.id}:${idx}`,
1650
+ chrome,
1651
+ strings,
1652
+ language,
1653
+ onSelect: () => onSelectField(field.id),
1654
+ onFieldChange,
1655
+ onMoveField,
1656
+ onDuplicateField,
1657
+ onDeleteField,
1658
+ dropZoneHandlers: getDropZoneHandlers(section.id, idx),
1659
+ dragHandleProps: getDragHandleProps(section.id, idx)
1660
+ },
1661
+ field.id
1662
+ )) }))
1663
+ ]
1664
+ }
1665
+ );
1666
+ }
1667
+
1668
+ // src/components/Canvas.tsx
1669
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1670
+ function Canvas({
1671
+ sections,
1672
+ activeSectionId,
1673
+ submitMode,
1674
+ submitStyle,
1675
+ selectedId,
1676
+ dragOverKey,
1677
+ chrome,
1678
+ strings,
1679
+ language,
1680
+ onActivateSection,
1681
+ onToggleSectionCollapse,
1682
+ onUpdateSectionTitle,
1683
+ onUpdateSectionBackground,
1684
+ onDuplicateSection,
1685
+ onMoveSection,
1686
+ onDeleteSection,
1687
+ onUpdateSectionSubmitStyle,
1688
+ onClearSectionSubmitStyle,
1689
+ onAddSection,
1690
+ onSelectField,
1691
+ onFieldChange,
1692
+ onMoveField,
1693
+ onDuplicateField,
1694
+ onDeleteField,
1695
+ getDropZoneHandlers,
1696
+ getDragHandleProps
1697
+ }) {
1698
+ return /* @__PURE__ */ jsxs8("div", { style: styles.canvas, children: [
1699
+ sections.map((section, sIdx) => /* @__PURE__ */ jsx9(
1700
+ SectionCard,
1701
+ {
1702
+ section,
1703
+ sIdx,
1704
+ sectionsLength: sections.length,
1705
+ isActive: activeSectionId === section.id,
1706
+ submitMode,
1707
+ submitStyle,
1708
+ selectedId,
1709
+ dragOverKey,
1710
+ chrome,
1711
+ strings,
1712
+ language,
1713
+ onActivate: () => onActivateSection(section.id),
1714
+ onToggleCollapse: () => onToggleSectionCollapse(section.id),
1715
+ onUpdateTitle: (value) => onUpdateSectionTitle(section.id, value),
1716
+ onUpdateBackground: (color) => onUpdateSectionBackground(section.id, color),
1717
+ onDuplicateSection: () => onDuplicateSection(section.id),
1718
+ onMoveSection: (dir) => onMoveSection(section.id, dir),
1719
+ onDeleteSection: () => onDeleteSection(section.id),
1720
+ onUpdateSectionSubmitStyle: (patch) => onUpdateSectionSubmitStyle(section.id, patch),
1721
+ onClearSectionSubmitStyle: () => onClearSectionSubmitStyle(section.id),
1722
+ onSelectField: (fieldId) => onSelectField(section.id, fieldId),
1723
+ onFieldChange,
1724
+ onMoveField,
1725
+ onDuplicateField,
1726
+ onDeleteField,
1727
+ getDropZoneHandlers,
1728
+ getDragHandleProps
1729
+ },
1730
+ section.id
1731
+ )),
1732
+ /* @__PURE__ */ jsxs8("button", { style: styles.addSectionBtn, onClick: onAddSection, children: [
1733
+ /* @__PURE__ */ jsx9(Layers, { size: 14 }),
1734
+ " ",
1735
+ chrome.addSection
1736
+ ] })
1737
+ ] });
1738
+ }
1739
+
1740
+ // src/components/Inspector.tsx
1741
+ import { Bold, Italic, X, Plus as Plus2, Trash2 as Trash23 } from "lucide-react";
1742
+
1743
+ // src/components/fields/DefaultValueEditor.tsx
1744
+ import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
1745
+ function DefaultValueEditor({ field, meta, lang, chrome, onChange }) {
1746
+ if (meta.isContent) return null;
1747
+ const f = field;
1748
+ if (meta.boolean) {
1749
+ return /* @__PURE__ */ jsxs9(Fragment2, { children: [
1750
+ /* @__PURE__ */ jsx10("label", { style: styles.propLabel, children: chrome.defaultValue }),
1751
+ /* @__PURE__ */ jsxs9("button", { style: styles.toggleRow, onClick: () => onChange({ defaultValue: !f.defaultValue }), children: [
1752
+ /* @__PURE__ */ jsx10("span", { style: { ...styles.toggleTrack, background: f.defaultValue ? "var(--fb-primary)" : "var(--fb-border)" }, children: /* @__PURE__ */ jsx10("span", { style: { ...styles.toggleThumb, transform: f.defaultValue ? "translateX(16px)" : "translateX(0)" } }) }),
1753
+ /* @__PURE__ */ jsx10("span", { style: { fontSize: 13, color: "#4A4D57" }, children: f.defaultValue ? chrome.checked : chrome.unchecked })
1754
+ ] })
1755
+ ] });
1756
+ }
1757
+ if (meta.multiValue) {
1758
+ const selectedVals = f.defaultValue || [];
1759
+ return /* @__PURE__ */ jsxs9(Fragment2, { children: [
1760
+ /* @__PURE__ */ jsx10("label", { style: styles.propLabel, children: chrome.defaultChecked }),
1761
+ /* @__PURE__ */ jsx10("div", { style: styles.optionList, children: (f.options || []).map((o) => {
1762
+ const checked = selectedVals.includes(o.value);
1763
+ return /* @__PURE__ */ jsxs9("label", { style: { ...styles.previewRadio, cursor: "pointer" }, children: [
1764
+ /* @__PURE__ */ jsx10("input", { type: "checkbox", checked, onChange: (e) => {
1765
+ const next = e.target.checked ? [...selectedVals, o.value] : selectedVals.filter((v) => v !== o.value);
1766
+ onChange({ defaultValue: next });
1767
+ }, style: { accentColor: "var(--fb-primary)" } }),
1768
+ t(o.label, lang)
1769
+ ] }, o.value);
1770
+ }) })
1771
+ ] });
1772
+ }
1773
+ if (meta.options) {
1774
+ return /* @__PURE__ */ jsxs9(Fragment2, { children: [
1775
+ /* @__PURE__ */ jsx10("label", { style: styles.propLabel, children: chrome.defaultValue }),
1776
+ /* @__PURE__ */ jsxs9("select", { style: styles.propInput, value: f.defaultValue || "", onChange: (e) => onChange({ defaultValue: e.target.value }), children: [
1777
+ /* @__PURE__ */ jsx10("option", { value: "", children: chrome.none }),
1778
+ (f.options || []).map((o) => /* @__PURE__ */ jsx10("option", { value: o.value, children: t(o.label, lang) }, o.value))
1779
+ ] })
1780
+ ] });
1781
+ }
1782
+ const htmlType = field.type === "input" && field.inputType === "number" ? "number" : field.type === "input" && field.inputType === "date" ? "date" : field.type === "input" && field.inputType === "time" ? "time" : "text";
1783
+ return /* @__PURE__ */ jsxs9(Fragment2, { children: [
1784
+ /* @__PURE__ */ jsx10("label", { style: styles.propLabel, children: chrome.defaultValue }),
1785
+ /* @__PURE__ */ jsx10("input", { type: htmlType, style: styles.propInput, value: f.defaultValue || "", onChange: (e) => onChange({ defaultValue: e.target.value }) })
1786
+ ] });
1787
+ }
1788
+
1789
+ // src/components/Inspector.tsx
1790
+ import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
1791
+ function Inspector({ selected, language, chrome, onUpdateField, onDeleteField, onUpdateOption, onAddOption, onRemoveOption }) {
1792
+ return /* @__PURE__ */ jsxs10("div", { style: styles.inspector, children: [
1793
+ /* @__PURE__ */ jsx11("div", { style: styles.panelHeading, children: chrome.properties }),
1794
+ !selected ? /* @__PURE__ */ jsx11("div", { style: styles.inspectorEmpty, children: chrome.selectFieldHint }) : /* @__PURE__ */ jsx11(
1795
+ InspectorBody,
1796
+ {
1797
+ selected,
1798
+ language,
1799
+ chrome,
1800
+ onUpdateField,
1801
+ onDeleteField,
1802
+ onUpdateOption,
1803
+ onAddOption,
1804
+ onRemoveOption
1805
+ }
1806
+ )
1807
+ ] });
1808
+ }
1809
+ function InspectorBody({ selected, language, chrome, onUpdateField, onDeleteField, onUpdateOption, onAddOption, onRemoveOption }) {
1810
+ const meta = getMeta(selected.type);
1811
+ const f = selected;
1812
+ return /* @__PURE__ */ jsxs10("div", { style: styles.inspectorBody, children: [
1813
+ !meta.isContent && /* @__PURE__ */ jsxs10(Fragment3, { children: [
1814
+ /* @__PURE__ */ jsx11("label", { style: styles.propLabel, children: chrome.label }),
1815
+ /* @__PURE__ */ jsx11("input", { style: styles.propInput, value: t(selected.label, language), onChange: (e) => onUpdateField({ label: withLang(selected.label, language, e.target.value) }) }),
1816
+ /* @__PURE__ */ jsxs10("label", { style: styles.toggleRow, children: [
1817
+ /* @__PURE__ */ jsx11("input", { type: "checkbox", checked: !!selected.hideLabel, onChange: (e) => onUpdateField({ hideLabel: e.target.checked }), style: { width: 15, height: 15, accentColor: "var(--fb-primary)" } }),
1818
+ /* @__PURE__ */ jsx11("span", { style: { fontSize: 13, color: "#4A4D57" }, children: chrome.hideLabel })
1819
+ ] })
1820
+ ] }),
1821
+ meta.hasSubtype && /* @__PURE__ */ jsxs10(Fragment3, { children: [
1822
+ /* @__PURE__ */ jsx11("label", { style: styles.propLabel, children: chrome.inputType }),
1823
+ /* @__PURE__ */ jsx11("select", { style: styles.propInput, value: f.inputType || "text", onChange: (e) => onUpdateField({ inputType: e.target.value }), children: INPUT_SUBTYPES.map((s) => /* @__PURE__ */ jsx11("option", { value: s, children: chrome[INPUT_SUBTYPE_CHROME_KEY[s]] }, s)) })
1824
+ ] }),
1825
+ selected.type === "paragraph" && /* @__PURE__ */ jsxs10(Fragment3, { children: [
1826
+ /* @__PURE__ */ jsx11("label", { style: styles.propLabel, children: chrome.content }),
1827
+ /* @__PURE__ */ jsx11("textarea", { style: { ...styles.propInput, minHeight: 70, resize: "vertical" }, value: t(selected.content, language), onChange: (e) => onUpdateField({ content: withLang(selected.content, language, e.target.value) }) }),
1828
+ /* @__PURE__ */ jsx11("div", { style: styles.inspectorDivider }),
1829
+ /* @__PURE__ */ jsx11("div", { style: styles.panelHeading, children: chrome.typography }),
1830
+ /* @__PURE__ */ jsx11("label", { style: styles.propLabel, children: chrome.headingStyle }),
1831
+ /* @__PURE__ */ jsx11(Segmented, { options: PARAGRAPH_TAG_OPTIONS.map((v) => ({ value: v, label: TAG_CHROME_KEY[v] ? chrome[TAG_CHROME_KEY[v]] : v.toUpperCase() })), value: selected.tag || "p", onChange: (v) => onUpdateField({ tag: v, ...TAG_PRESETS[v] }) }),
1832
+ /* @__PURE__ */ jsx11("label", { style: styles.propLabel, children: chrome.width }),
1833
+ /* @__PURE__ */ jsx11(Segmented, { options: FONT_SIZE_OPTIONS, value: selected.fontSize || "md", onChange: (v) => onUpdateField({ fontSize: v }) }),
1834
+ /* @__PURE__ */ jsx11("label", { style: styles.propLabel, children: chrome.style }),
1835
+ /* @__PURE__ */ jsxs10("div", { style: styles.styleToggleRow, children: [
1836
+ /* @__PURE__ */ jsx11("button", { type: "button", style: selected.fontWeight === "bold" ? styles.styleToggleActive : styles.styleToggle, onClick: () => onUpdateField({ fontWeight: selected.fontWeight === "bold" ? "normal" : "bold" }), children: /* @__PURE__ */ jsx11(Bold, { size: 14 }) }),
1837
+ /* @__PURE__ */ jsx11("button", { type: "button", style: selected.fontStyle === "italic" ? styles.styleToggleActive : styles.styleToggle, onClick: () => onUpdateField({ fontStyle: selected.fontStyle === "italic" ? "normal" : "italic" }), children: /* @__PURE__ */ jsx11(Italic, { size: 14 }) })
1838
+ ] }),
1839
+ /* @__PURE__ */ jsx11("label", { style: styles.propLabel, children: chrome.align }),
1840
+ /* @__PURE__ */ jsx11(Segmented, { options: TEXT_ALIGN_OPTIONS, value: selected.textAlign || "left", onChange: (v) => onUpdateField({ textAlign: v }) }),
1841
+ /* @__PURE__ */ jsx11("label", { style: styles.propLabel, children: chrome.color }),
1842
+ /* @__PURE__ */ jsxs10("div", { style: styles.swatchRow, children: [
1843
+ COLOR_SWATCHES.map((c) => /* @__PURE__ */ jsx11("button", { type: "button", title: c || chrome.none, style: { ...styles.swatchBtn, background: c || "var(--fb-ink)", ...selected.color === c ? styles.swatchBtnActive : {} }, onClick: () => onUpdateField({ color: c }) }, c || "default")),
1844
+ /* @__PURE__ */ jsx11("input", { type: "color", value: selected.color || "#1B1E24", onChange: (e) => onUpdateField({ color: e.target.value }), style: styles.colorPickerInput })
1845
+ ] })
1846
+ ] }),
1847
+ selected.type === "image" && /* @__PURE__ */ jsxs10(Fragment3, { children: [
1848
+ /* @__PURE__ */ jsx11("label", { style: styles.propLabel, children: chrome.imageUrl }),
1849
+ /* @__PURE__ */ jsx11("input", { style: styles.propInput, placeholder: "https://...", value: selected.src || "", onChange: (e) => onUpdateField({ src: e.target.value }) }),
1850
+ /* @__PURE__ */ jsx11("p", { style: styles.helperText, children: chrome.imageUrlHelper }),
1851
+ /* @__PURE__ */ jsx11("label", { style: styles.propLabel, children: chrome.altText }),
1852
+ /* @__PURE__ */ jsx11("input", { style: styles.propInput, value: t(selected.alt, language), onChange: (e) => onUpdateField({ alt: withLang(selected.alt, language, e.target.value) }) }),
1853
+ /* @__PURE__ */ jsx11("label", { style: styles.propLabel, children: chrome.linkOptional }),
1854
+ /* @__PURE__ */ jsx11("input", { style: styles.propInput, placeholder: "https://...", value: selected.link || "", onChange: (e) => onUpdateField({ link: e.target.value }) }),
1855
+ /* @__PURE__ */ jsx11("label", { style: styles.propLabel, children: chrome.shape }),
1856
+ /* @__PURE__ */ jsx11(Segmented, { options: IMAGE_SHAPES.map((v) => ({ value: v, label: chrome[IMAGE_SHAPE_CHROME_KEY[v]] })), value: selected.shape || "square", onChange: (v) => onUpdateField({ shape: v }) }),
1857
+ /* @__PURE__ */ jsx11("p", { style: styles.helperText, children: "Fills the field's own width (set below under Layout) and scales height automatically \u2014 always responsive, never overflows." })
1858
+ ] }),
1859
+ meta.placeholder && /* @__PURE__ */ jsxs10(Fragment3, { children: [
1860
+ /* @__PURE__ */ jsx11("label", { style: styles.propLabel, children: chrome.placeholder }),
1861
+ /* @__PURE__ */ jsx11("input", { style: styles.propInput, value: t(f.placeholder, language), onChange: (e) => onUpdateField({ placeholder: withLang(f.placeholder, language, e.target.value) }) })
1862
+ ] }),
1863
+ selected.type === "input" && (selected.inputType === "text" || selected.inputType === "password" || !selected.inputType) && /* @__PURE__ */ jsxs10(Fragment3, { children: [
1864
+ /* @__PURE__ */ jsx11("label", { style: styles.propLabel, children: chrome.maxLength }),
1865
+ /* @__PURE__ */ jsx11("input", { type: "number", style: styles.propInput, placeholder: chrome.noLimit, value: selected.maxLength || "", onChange: (e) => onUpdateField({ maxLength: e.target.value ? Number(e.target.value) : null }) })
1866
+ ] }),
1867
+ selected.type === "input" && selected.inputType === "number" && /* @__PURE__ */ jsxs10(Fragment3, { children: [
1868
+ /* @__PURE__ */ jsx11("label", { style: styles.propLabel, children: chrome.minValue }),
1869
+ /* @__PURE__ */ jsx11("input", { type: "number", style: styles.propInput, value: selected.min ?? "", onChange: (e) => onUpdateField({ min: e.target.value === "" ? null : Number(e.target.value) }) }),
1870
+ /* @__PURE__ */ jsx11("label", { style: styles.propLabel, children: chrome.maxValue }),
1871
+ /* @__PURE__ */ jsx11("input", { type: "number", style: styles.propInput, value: selected.max ?? "", onChange: (e) => onUpdateField({ max: e.target.value === "" ? null : Number(e.target.value) }) })
1872
+ ] }),
1873
+ selected.type === "textarea" && /* @__PURE__ */ jsxs10(Fragment3, { children: [
1874
+ /* @__PURE__ */ jsx11("label", { style: styles.propLabel, children: chrome.maxLength }),
1875
+ /* @__PURE__ */ jsx11("input", { type: "number", style: styles.propInput, placeholder: chrome.noLimit, value: selected.maxLength || "", onChange: (e) => onUpdateField({ maxLength: e.target.value ? Number(e.target.value) : null }) })
1876
+ ] }),
1877
+ meta.options && /* @__PURE__ */ jsxs10(Fragment3, { children: [
1878
+ /* @__PURE__ */ jsx11("label", { style: styles.propLabel, children: chrome.options }),
1879
+ /* @__PURE__ */ jsxs10("div", { style: styles.optionList, children: [
1880
+ (f.options || []).map((opt, i) => /* @__PURE__ */ jsxs10("div", { style: styles.optionRow, children: [
1881
+ /* @__PURE__ */ jsx11("input", { style: styles.optionInput, value: t(opt.label, language), onChange: (e) => onUpdateOption(i, { label: withLang(opt.label, language, e.target.value) }) }),
1882
+ /* @__PURE__ */ jsx11("button", { style: styles.iconBtn, onClick: () => onRemoveOption(i), disabled: (f.options || []).length <= 1, children: /* @__PURE__ */ jsx11(X, { size: 13 }) })
1883
+ ] }, i)),
1884
+ /* @__PURE__ */ jsxs10("button", { style: styles.addOptionBtn, onClick: onAddOption, children: [
1885
+ /* @__PURE__ */ jsx11(Plus2, { size: 13 }),
1886
+ " ",
1887
+ chrome.addOption
1888
+ ] })
1889
+ ] })
1890
+ ] }),
1891
+ /* @__PURE__ */ jsx11(DefaultValueEditor, { field: selected, meta, lang: language, chrome, onChange: onUpdateField }),
1892
+ !meta.isContent && /* @__PURE__ */ jsxs10("label", { style: styles.toggleRow, children: [
1893
+ /* @__PURE__ */ jsx11("input", { type: "checkbox", checked: !!selected.required, onChange: (e) => onUpdateField({ required: e.target.checked }), style: { width: 15, height: 15, accentColor: "var(--fb-primary)" } }),
1894
+ /* @__PURE__ */ jsx11("span", { style: { fontSize: 13, color: "#4A4D57" }, children: chrome.requiredField })
1895
+ ] }),
1896
+ /* @__PURE__ */ jsx11("div", { style: styles.inspectorDivider }),
1897
+ /* @__PURE__ */ jsx11("div", { style: styles.panelHeading, children: chrome.layout }),
1898
+ /* @__PURE__ */ jsx11("label", { style: styles.propLabel, children: chrome.width }),
1899
+ /* @__PURE__ */ jsx11(Segmented, { options: WIDTH_OPTIONS.map((o) => ({ ...o, label: o.labelKey ? chrome[o.labelKey] : o.label || "" })), value: selected.width || "1/1", onChange: (v) => onUpdateField({ width: v }) }),
1900
+ /* @__PURE__ */ jsx11("label", { style: styles.propLabel, children: chrome.verticalAlign }),
1901
+ /* @__PURE__ */ jsx11(Segmented, { options: [{ value: "top", label: chrome.top }, { value: "middle", label: chrome.middle }, { value: "bottom", label: chrome.bottom }], value: selected.verticalAlign || "top", onChange: (v) => onUpdateField({ verticalAlign: v }) }),
1902
+ !meta.boolean && !meta.isContent && /* @__PURE__ */ jsxs10(Fragment3, { children: [
1903
+ /* @__PURE__ */ jsx11("label", { style: styles.propLabel, children: chrome.labelPosition }),
1904
+ /* @__PURE__ */ jsx11(Segmented, { options: [{ value: "top", label: chrome.above }, { value: "inline", label: chrome.inline }], value: selected.labelPosition || "top", onChange: (v) => onUpdateField({ labelPosition: v }) })
1905
+ ] }),
1906
+ !meta.isContent && /* @__PURE__ */ jsxs10(Fragment3, { children: [
1907
+ /* @__PURE__ */ jsx11("div", { style: styles.inspectorDivider }),
1908
+ /* @__PURE__ */ jsx11("div", { style: styles.panelHeading, children: chrome.icon }),
1909
+ /* @__PURE__ */ jsxs10("label", { style: styles.toggleRow, children: [
1910
+ /* @__PURE__ */ jsx11("input", { type: "checkbox", checked: !!selected.showIcon, onChange: (e) => onUpdateField({ showIcon: e.target.checked }), style: { width: 15, height: 15, accentColor: "var(--fb-primary)" } }),
1911
+ /* @__PURE__ */ jsx11("span", { style: { fontSize: 13, color: "#4A4D57" }, children: chrome.showIcon })
1912
+ ] }),
1913
+ selected.showIcon && /* @__PURE__ */ jsx11("div", { style: styles.iconGrid, children: Object.keys(ICON_LIBRARY).map((key) => {
1914
+ const IconComp = ICON_LIBRARY[key];
1915
+ const active = selected.displayIcon === key;
1916
+ return /* @__PURE__ */ jsx11("button", { type: "button", title: key, style: active ? styles.iconGridBtnActive : styles.iconGridBtn, onClick: () => onUpdateField({ displayIcon: key }), children: /* @__PURE__ */ jsx11(IconComp, { size: 15 }) }, key);
1917
+ }) })
1918
+ ] }),
1919
+ /* @__PURE__ */ jsx11("div", { style: styles.inspectorDivider }),
1920
+ /* @__PURE__ */ jsxs10("button", { style: styles.deleteFieldBtn, onClick: onDeleteField, children: [
1921
+ /* @__PURE__ */ jsx11(Trash23, { size: 13 }),
1922
+ " ",
1923
+ chrome.deleteField
1924
+ ] })
1925
+ ] });
1926
+ }
1927
+
1928
+ // src/components/PreviewPane.tsx
1929
+ import { useState as useState6 } from "react";
1930
+ import { PartyPopper, X as X2, CircleAlert as CircleAlert2 } from "lucide-react";
1931
+
1932
+ // src/lib/validate.ts
1933
+ function validateField(field, value, strings) {
1934
+ const meta = getMeta(field.type);
1935
+ if (meta.isContent) return null;
1936
+ const isEmpty = value === void 0 || value === null || value === "" || Array.isArray(value) && value.length === 0 || !!meta.boolean && !!field.required && value !== true;
1937
+ if (field.required && isEmpty) return strings.requiredError;
1938
+ if (isEmpty) return null;
1939
+ if (field.type === "input" && field.inputType === "email" && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(value))) return strings.invalidEmail;
1940
+ if (field.type === "input" && field.inputType === "phone" && !/^[0-9+\-\s()]{7,20}$/.test(String(value))) return strings.invalidPhone;
1941
+ if (field.type === "input" && field.inputType === "number") {
1942
+ const num = Number(value);
1943
+ if (field.min !== void 0 && field.min !== null && field.min !== "" && num < Number(field.min)) return strings.tooSmall(field.min);
1944
+ if (field.max !== void 0 && field.max !== null && field.max !== "" && num > Number(field.max)) return strings.tooLarge(field.max);
1945
+ }
1946
+ if ((field.type === "textarea" || field.type === "input" && !["number", "date", "time"].includes(field.inputType)) && field.maxLength && String(value).length > field.maxLength) {
1947
+ return strings.tooLong(field.maxLength);
1948
+ }
1949
+ return null;
1950
+ }
1951
+
1952
+ // src/lib/format.ts
1953
+ function formatValue(field, lang) {
1954
+ const v = field.defaultValue;
1955
+ if (Array.isArray(v)) {
1956
+ if (v.length === 0) return "\u2014";
1957
+ return v.map((val) => t(field.options?.find((o) => o.value === val)?.label, lang) || val).join(", ");
1958
+ }
1959
+ if (typeof v === "boolean") return v ? "Yes" : "No";
1960
+ if (field.options && v) return t(field.options.find((o) => o.value === v)?.label, lang) || v;
1961
+ if (v === void 0 || v === null || v === "") return "\u2014";
1962
+ return String(v);
1963
+ }
1964
+
1965
+ // src/components/PreviewPane.tsx
1966
+ import { Fragment as Fragment4, jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
1967
+ function PreviewPane({ title, sections, onFieldChange, language, strings, chrome, baseMaxWidth, submitLabel, submitMode, submitStyle }) {
1968
+ const [device, setDevice] = useState6("laptop");
1969
+ const [errors, setErrors] = useState6({});
1970
+ const [submitted, setSubmitted] = useState6(null);
1971
+ const deviceOptions = buildDeviceOptions(baseMaxWidth, chrome);
1972
+ const maxWidth = (deviceOptions.find((d) => d.value === device) || deviceOptions[0]).maxWidth;
1973
+ const allFields = sections.flatMap((s) => s.fields);
1974
+ const submitText = t(submitLabel, language) || strings.submit;
1975
+ const overallHasFormFields = allFields.some((f) => !getMeta(f.type).isContent);
1976
+ function runValidation(fields) {
1977
+ const errs = {};
1978
+ fields.forEach((f) => {
1979
+ const e = validateField(f, f.defaultValue, strings);
1980
+ if (e) errs[f.id] = e;
1981
+ });
1982
+ return errs;
1983
+ }
1984
+ function handleSubmitAll() {
1985
+ const errs = runValidation(allFields);
1986
+ setErrors(errs);
1987
+ if (Object.keys(errs).length > 0) {
1988
+ setSubmitted(null);
1989
+ return;
1990
+ }
1991
+ setSubmitted(allFields.filter((f) => !getMeta(f.type).isContent).map((f) => ({ label: t(f.label, language), value: formatValue(f, language) })));
1992
+ }
1993
+ function handleSubmitSection(section) {
1994
+ const errs = runValidation(section.fields);
1995
+ setErrors((prev) => {
1996
+ const next = { ...prev };
1997
+ section.fields.forEach((f) => delete next[f.id]);
1998
+ return { ...next, ...errs };
1999
+ });
2000
+ if (Object.keys(errs).length > 0) {
2001
+ setSubmitted(null);
2002
+ return;
2003
+ }
2004
+ setSubmitted(section.fields.filter((f) => !getMeta(f.type).isContent).map((f) => ({ label: t(f.label, language), value: formatValue(f, language) })));
2005
+ }
2006
+ return /* @__PURE__ */ jsxs11("div", { style: styles.previewWrap, children: [
2007
+ /* @__PURE__ */ jsxs11("div", { style: { width: "100%", maxWidth, display: "flex", flexDirection: "column", alignItems: "center" }, children: [
2008
+ /* @__PURE__ */ jsx12("div", { style: styles.previewToolbar, children: /* @__PURE__ */ jsx12(Segmented, { options: deviceOptions, value: device, onChange: setDevice }) }),
2009
+ /* @__PURE__ */ jsxs11("div", { style: { ...styles.previewCard, maxWidth, width: "100%" }, children: [
2010
+ /* @__PURE__ */ jsx12("h2", { style: styles.previewTitle, children: t(title, language) }),
2011
+ allFields.length === 0 && /* @__PURE__ */ jsx12("p", { style: { color: "var(--fb-muted)", fontSize: 14 }, children: strings.addFieldsHint }),
2012
+ sections.map((section) => {
2013
+ const sectionHasFormFields = section.fields.some((f) => !getMeta(f.type).isContent);
2014
+ const sectionHasErrors = section.fields.some((f) => errors[f.id]);
2015
+ return /* @__PURE__ */ jsxs11("div", { style: { background: section.background || "transparent", padding: section.background ? 16 : 0, borderRadius: section.background ? 10 : 0, marginBottom: "var(--fb-space-section)" }, children: [
2016
+ t(section.title, language) && /* @__PURE__ */ jsx12("h3", { style: styles.sectionRuntimeTitle, children: t(section.title, language) }),
2017
+ /* @__PURE__ */ jsx12("div", { style: styles.previewGrid, children: section.fields.map((field) => {
2018
+ const width = effectiveWidth(field.width, device);
2019
+ const flexBasis = `1 1 calc(${WIDTH_PERCENT[width]} - 14px)`;
2020
+ return /* @__PURE__ */ jsx12("div", { style: { flex: flexBasis, minWidth: 0, alignSelf: ALIGN_MAP[field.verticalAlign || "top"] }, children: /* @__PURE__ */ jsx12(FieldBlock, { field, lang: language, onFieldChange, strings, chrome, error: errors[field.id] }) }, field.id);
2021
+ }) }),
2022
+ submitMode === "perSection" && sectionHasFormFields && /* @__PURE__ */ jsxs11(Fragment4, { children: [
2023
+ /* @__PURE__ */ jsx12("button", { style: { ...styles.submitBtn, ...resolveSubmitStyle(section.submitStyle || submitStyle) }, onClick: () => handleSubmitSection(section), children: submitText }),
2024
+ sectionHasErrors && /* @__PURE__ */ jsxs11("p", { style: styles.formErrorNote, children: [
2025
+ /* @__PURE__ */ jsx12(CircleAlert2, { size: 13 }),
2026
+ " ",
2027
+ strings.fixErrors
2028
+ ] })
2029
+ ] })
2030
+ ] }, section.id);
2031
+ }),
2032
+ submitMode === "combined" && overallHasFormFields && /* @__PURE__ */ jsx12("button", { style: { ...styles.submitBtn, ...resolveSubmitStyle(submitStyle) }, onClick: handleSubmitAll, children: submitText }),
2033
+ submitMode === "combined" && Object.keys(errors).length > 0 && /* @__PURE__ */ jsxs11("p", { style: styles.formErrorNote, children: [
2034
+ /* @__PURE__ */ jsx12(CircleAlert2, { size: 13 }),
2035
+ " ",
2036
+ strings.fixErrors
2037
+ ] })
2038
+ ] })
2039
+ ] }),
2040
+ submitted && /* @__PURE__ */ jsx12("div", { style: styles.modalOverlay, onClick: () => setSubmitted(null), children: /* @__PURE__ */ jsxs11("div", { style: { ...styles.modal, width: 420 }, onClick: (e) => e.stopPropagation(), children: [
2041
+ /* @__PURE__ */ jsxs11("div", { style: styles.modalHeader, children: [
2042
+ /* @__PURE__ */ jsxs11("span", { style: { fontWeight: 600, fontSize: 14, display: "flex", alignItems: "center", gap: 7 }, children: [
2043
+ /* @__PURE__ */ jsx12(PartyPopper, { size: 15, color: "var(--fb-primary)" }),
2044
+ " ",
2045
+ strings.submittedTitle
2046
+ ] }),
2047
+ /* @__PURE__ */ jsx12("button", { style: styles.iconBtn, onClick: () => setSubmitted(null), children: /* @__PURE__ */ jsx12(X2, { size: 16 }) })
2048
+ ] }),
2049
+ /* @__PURE__ */ jsxs11("div", { style: { padding: 16 }, children: [
2050
+ /* @__PURE__ */ jsx12("p", { style: { fontSize: 12.5, color: "var(--fb-muted)", marginTop: 0 }, children: strings.submittedBody }),
2051
+ /* @__PURE__ */ jsx12("div", { style: styles.submittedList, children: submitted.map((row, i) => /* @__PURE__ */ jsxs11("div", { style: styles.submittedRow, children: [
2052
+ /* @__PURE__ */ jsx12("span", { style: styles.submittedLabel, children: row.label }),
2053
+ /* @__PURE__ */ jsx12("span", { style: styles.submittedValue, children: row.value })
2054
+ ] }, i)) }),
2055
+ /* @__PURE__ */ jsx12("button", { style: { ...styles.primaryBtn, width: "100%", justifyContent: "center", marginTop: 14 }, onClick: () => setSubmitted(null), children: strings.close })
2056
+ ] })
2057
+ ] }) })
2058
+ ] });
2059
+ }
2060
+
2061
+ // src/components/modals/JsonModal.tsx
2062
+ import { Check, ClipboardCopy, X as X3 } from "lucide-react";
2063
+ import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
2064
+ function JsonModal({ chrome, jsonString, copied, onCopy, onClose }) {
2065
+ return /* @__PURE__ */ jsx13("div", { style: styles.modalOverlay, onClick: onClose, children: /* @__PURE__ */ jsxs12("div", { style: styles.modal, onClick: (e) => e.stopPropagation(), children: [
2066
+ /* @__PURE__ */ jsxs12("div", { style: styles.modalHeader, children: [
2067
+ /* @__PURE__ */ jsx13("span", { style: { fontWeight: 600, fontSize: 14 }, children: chrome.formJson }),
2068
+ /* @__PURE__ */ jsxs12("div", { style: { display: "flex", gap: 8 }, children: [
2069
+ /* @__PURE__ */ jsxs12("button", { style: styles.ghostBtn, onClick: onCopy, children: [
2070
+ copied ? /* @__PURE__ */ jsx13(Check, { size: 14 }) : /* @__PURE__ */ jsx13(ClipboardCopy, { size: 14 }),
2071
+ copied ? chrome.copied : chrome.copy
2072
+ ] }),
2073
+ /* @__PURE__ */ jsx13("button", { style: styles.iconBtn, onClick: onClose, children: /* @__PURE__ */ jsx13(X3, { size: 16 }) })
2074
+ ] })
2075
+ ] }),
2076
+ /* @__PURE__ */ jsx13("pre", { style: styles.jsonPre, children: jsonString })
2077
+ ] }) });
2078
+ }
2079
+
2080
+ // src/components/modals/LibraryModal.tsx
2081
+ import { Trash2 as Trash24, X as X4 } from "lucide-react";
2082
+ import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
2083
+ function LibraryModal({ chrome, savedForms, currentFormId, onOpen, onDelete, onClose }) {
2084
+ return /* @__PURE__ */ jsx14("div", { style: styles.modalOverlay, onClick: onClose, children: /* @__PURE__ */ jsxs13("div", { style: styles.modal, onClick: (e) => e.stopPropagation(), children: [
2085
+ /* @__PURE__ */ jsxs13("div", { style: styles.modalHeader, children: [
2086
+ /* @__PURE__ */ jsx14("span", { style: { fontWeight: 600, fontSize: 14 }, children: chrome.myForms }),
2087
+ /* @__PURE__ */ jsx14("button", { style: styles.iconBtn, onClick: onClose, children: /* @__PURE__ */ jsx14(X4, { size: 16 }) })
2088
+ ] }),
2089
+ /* @__PURE__ */ jsx14("div", { style: styles.libraryBody, children: savedForms.length === 0 ? /* @__PURE__ */ jsx14("div", { style: styles.inspectorEmpty, children: chrome.nothingSaved }) : savedForms.map((f) => /* @__PURE__ */ jsxs13("div", { style: styles.libraryRow, children: [
2090
+ /* @__PURE__ */ jsxs13("div", { style: { minWidth: 0 }, children: [
2091
+ /* @__PURE__ */ jsxs13("div", { style: styles.libraryRowTitle, children: [
2092
+ f.title,
2093
+ f.id === currentFormId && /* @__PURE__ */ jsx14("span", { style: styles.currentBadge, children: chrome.current })
2094
+ ] }),
2095
+ /* @__PURE__ */ jsxs13("div", { style: styles.libraryRowMeta, children: [
2096
+ chrome.updated,
2097
+ " ",
2098
+ new Date(f.updatedAt).toLocaleString()
2099
+ ] })
2100
+ ] }),
2101
+ /* @__PURE__ */ jsxs13("div", { style: { display: "flex", gap: 6, flexShrink: 0 }, children: [
2102
+ /* @__PURE__ */ jsx14("button", { style: styles.ghostBtn, onClick: () => onOpen(f.id), children: chrome.open }),
2103
+ /* @__PURE__ */ jsx14("button", { style: { ...styles.iconBtn, ...styles.iconBtnDanger }, title: chrome.delete, onClick: () => onDelete(f.id), children: /* @__PURE__ */ jsx14(Trash24, { size: 13 }) })
2104
+ ] })
2105
+ ] }, f.id)) })
2106
+ ] }) });
2107
+ }
2108
+
2109
+ // src/components/modals/SaveAsModal.tsx
2110
+ import { useState as useState7 } from "react";
2111
+ import { Save as Save2, X as X5 } from "lucide-react";
2112
+ import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
2113
+ function SaveAsModal({ chrome, suggestedName, onSave, onClose }) {
2114
+ const [name, setName] = useState7(suggestedName);
2115
+ return /* @__PURE__ */ jsx15("div", { style: styles.modalOverlay, onClick: onClose, children: /* @__PURE__ */ jsxs14("div", { style: { ...styles.modal, width: 380 }, onClick: (e) => e.stopPropagation(), children: [
2116
+ /* @__PURE__ */ jsxs14("div", { style: styles.modalHeader, children: [
2117
+ /* @__PURE__ */ jsx15("span", { style: { fontWeight: 600, fontSize: 14 }, children: chrome.saveForm }),
2118
+ /* @__PURE__ */ jsx15("button", { style: styles.iconBtn, onClick: onClose, children: /* @__PURE__ */ jsx15(X5, { size: 16 }) })
2119
+ ] }),
2120
+ /* @__PURE__ */ jsxs14("div", { style: { padding: 16 }, children: [
2121
+ /* @__PURE__ */ jsx15("label", { style: styles.propLabel, children: chrome.formName }),
2122
+ /* @__PURE__ */ jsx15("input", { autoFocus: true, style: styles.propInput, value: name, onChange: (e) => setName(e.target.value), onKeyDown: (e) => e.key === "Enter" && name.trim() && onSave(name.trim()) }),
2123
+ /* @__PURE__ */ jsxs14("button", { style: { ...styles.primaryBtn, width: "100%", justifyContent: "center", marginTop: 14 }, disabled: !name.trim(), onClick: () => onSave(name.trim()), children: [
2124
+ /* @__PURE__ */ jsx15(Save2, { size: 14 }),
2125
+ " ",
2126
+ chrome.saveToLibrary
2127
+ ] })
2128
+ ] })
2129
+ ] }) });
2130
+ }
2131
+
2132
+ // src/components/modals/SettingsModal.tsx
2133
+ import { X as X6 } from "lucide-react";
2134
+ import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
2135
+ function SettingsModal({
2136
+ chrome,
2137
+ strings,
2138
+ submitLabel,
2139
+ language,
2140
+ submitMode,
2141
+ submitStyle,
2142
+ onSubmitLabelChange,
2143
+ onSubmitModeChange,
2144
+ onSubmitStyleChange,
2145
+ onClose
2146
+ }) {
2147
+ return /* @__PURE__ */ jsx16("div", { style: styles.modalOverlay, onClick: onClose, children: /* @__PURE__ */ jsxs15("div", { style: { ...styles.modal, width: 400 }, onClick: (e) => e.stopPropagation(), children: [
2148
+ /* @__PURE__ */ jsxs15("div", { style: styles.modalHeader, children: [
2149
+ /* @__PURE__ */ jsx16("span", { style: { fontWeight: 600, fontSize: 14 }, children: chrome.formSettings }),
2150
+ /* @__PURE__ */ jsx16("button", { style: styles.iconBtn, onClick: onClose, children: /* @__PURE__ */ jsx16(X6, { size: 16 }) })
2151
+ ] }),
2152
+ /* @__PURE__ */ jsxs15("div", { style: { padding: 16 }, children: [
2153
+ /* @__PURE__ */ jsx16("div", { style: styles.panelHeading, children: chrome.submitSectionTitle }),
2154
+ /* @__PURE__ */ jsx16("label", { style: styles.propLabel, children: chrome.submitLabel }),
2155
+ /* @__PURE__ */ jsx16("input", { style: styles.propInput, placeholder: strings.submit, value: t(submitLabel, language), onChange: (e) => onSubmitLabelChange(e.target.value) }),
2156
+ /* @__PURE__ */ jsx16("label", { style: styles.propLabel, children: chrome.submitMode }),
2157
+ /* @__PURE__ */ jsx16(Segmented, { options: [{ value: "combined", label: chrome.combined }, { value: "perSection", label: chrome.perSection }], value: submitMode, onChange: onSubmitModeChange }),
2158
+ /* @__PURE__ */ jsx16("div", { style: styles.inspectorDivider }),
2159
+ /* @__PURE__ */ jsx16("div", { style: styles.panelHeading, children: chrome.submitStyle }),
2160
+ /* @__PURE__ */ jsx16("label", { style: styles.propLabel, children: chrome.color }),
2161
+ /* @__PURE__ */ jsxs15("div", { style: styles.swatchRow, children: [
2162
+ BUTTON_COLOR_SWATCHES.map((c) => /* @__PURE__ */ jsx16("button", { type: "button", title: c || chrome.none, style: { ...styles.swatchBtn, background: c || "var(--fb-primary)", ...(submitStyle.color || "") === c ? styles.swatchBtnActive : {} }, onClick: () => onSubmitStyleChange({ color: c }) }, c || "default")),
2163
+ /* @__PURE__ */ jsx16("input", { type: "color", value: submitStyle.color || "#5B5FEF", onChange: (e) => onSubmitStyleChange({ color: e.target.value }), style: styles.colorPickerInput })
2164
+ ] }),
2165
+ /* @__PURE__ */ jsx16("label", { style: styles.propLabel, children: chrome.buttonSize }),
2166
+ /* @__PURE__ */ jsx16(Segmented, { options: SUBMIT_SIZE_OPTIONS, value: submitStyle.size, onChange: (v) => onSubmitStyleChange({ size: v }) })
2167
+ ] })
2168
+ ] }) });
2169
+ }
2170
+
2171
+ // src/styles/globalCss.ts
2172
+ var css = `
2173
+ * { box-sizing: border-box; }
2174
+ button { font-family: inherit; cursor: pointer; }
2175
+ input, textarea, select { font-family: inherit; }
2176
+ @keyframes fb-spin { to { transform: rotate(360deg); } }
2177
+ .spin { animation: fb-spin 0.8s linear infinite; }
2178
+ `;
2179
+
2180
+ // src/FormBuilder.tsx
2181
+ import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
2182
+ function FormBuilder({
2183
+ theme: themeOverrideProp,
2184
+ language: languageOverride,
2185
+ languages = DEFAULT_LANGUAGES,
2186
+ strings: stringsOverride,
2187
+ chrome: chromeOverride,
2188
+ themeEditable = false,
2189
+ storage: storageProp
2190
+ } = {}) {
2191
+ const storage = storageProp ?? localStorageAdapter;
2192
+ const { theme, updateThemeColor, updateThemeLayout, resetTheme, replaceThemeOverrides, themeOverrides } = useTheme(themeOverrideProp);
2193
+ const [language, setLanguage] = useState8(languageOverride || languages[0]?.code || "en");
2194
+ const strings = { ...DEFAULT_STRINGS[language] || DEFAULT_STRINGS.en, ...stringsOverride && stringsOverride[language] || {} };
2195
+ const chrome = { ...CHROME[language] || CHROME.en, ...chromeOverride && chromeOverride[language] || {} };
2196
+ const doc = useFormDocument({ language, chrome });
2197
+ const drag = useDragReorder(doc.reorderWithinSection);
2198
+ const [mode, setMode] = useState8("build");
2199
+ const [showJson, setShowJson] = useState8(false);
2200
+ const [showSettings, setShowSettings] = useState8(false);
2201
+ const [showLibrary, setShowLibrary] = useState8(false);
2202
+ const [copied, setCopied] = useState8(false);
2203
+ const persistence = usePersistence({
2204
+ storage,
2205
+ language,
2206
+ document: {
2207
+ title: doc.title,
2208
+ submitLabel: doc.submitLabel,
2209
+ submitMode: doc.submitMode,
2210
+ submitStyle: doc.submitStyle,
2211
+ themeOverrides,
2212
+ sections: doc.sections
2213
+ },
2214
+ onLoadDocument: doc.loadDocument,
2215
+ onLoadThemeOverrides: replaceThemeOverrides,
2216
+ onTitleChange: doc.setTitle,
2217
+ onNewForm: () => {
2218
+ doc.resetToBlank();
2219
+ resetTheme();
2220
+ },
2221
+ ensureActiveSection: () => doc.setActiveSectionId((prev) => prev ?? doc.sections[0]?.id ?? null)
2222
+ });
2223
+ const jsonDoc = {
2224
+ version: 4,
2225
+ title: doc.title,
2226
+ submitLabel: doc.submitLabel,
2227
+ submitMode: doc.submitMode,
2228
+ submitStyle: doc.submitStyle,
2229
+ themeOverrides,
2230
+ sections: doc.sections.map((s) => ({ id: s.id, title: s.title, background: s.background, collapsed: s.collapsed, submitStyle: s.submitStyle, fields: s.fields }))
2231
+ };
2232
+ const jsonString = JSON.stringify(jsonDoc, null, 2);
2233
+ function copyJson() {
2234
+ navigator.clipboard.writeText(jsonString).then(() => {
2235
+ setCopied(true);
2236
+ setTimeout(() => setCopied(false), 1500);
2237
+ }).catch(() => {
2238
+ });
2239
+ }
2240
+ const activeSectionIdx = doc.sections.findIndex((s) => s.id === doc.activeSection?.id);
2241
+ const activeSectionLabel = t(doc.activeSection?.title, language) || `#${activeSectionIdx + 1}`;
2242
+ const rootStyle = {
2243
+ ...styles.app,
2244
+ "--fb-primary": theme.primary,
2245
+ "--fb-primary-soft": theme.primarySoft,
2246
+ "--fb-danger": theme.danger,
2247
+ "--fb-danger-soft": theme.dangerSoft,
2248
+ "--fb-ink": theme.ink,
2249
+ "--fb-muted": theme.muted,
2250
+ "--fb-border": theme.border,
2251
+ "--fb-surface": theme.surface,
2252
+ "--fb-canvas": theme.canvas,
2253
+ "--fb-page-bg": theme.pageBackground,
2254
+ "--fb-space-page": `${theme.layout.pagePadding}px`,
2255
+ "--fb-space-canvas": `${theme.layout.canvasPadding}px`,
2256
+ "--fb-space-section": `${theme.layout.sectionGap}px`,
2257
+ "--fb-space-field": `${theme.layout.fieldGap}px`,
2258
+ "--fb-space-toolbar": `${theme.layout.toolbarPadding}px`,
2259
+ "--fb-space-panel": `${theme.layout.panelPadding}px`,
2260
+ "--fb-space-ticket": `${theme.layout.ticketPadding}px`
2261
+ };
2262
+ return /* @__PURE__ */ jsxs16("div", { style: rootStyle, children: [
2263
+ /* @__PURE__ */ jsx17("style", { children: css }),
2264
+ /* @__PURE__ */ jsx17(
2265
+ Toolbar,
2266
+ {
2267
+ title: doc.title,
2268
+ language,
2269
+ languages,
2270
+ mode,
2271
+ saveState: persistence.saveState,
2272
+ chrome,
2273
+ themeEditable,
2274
+ theme,
2275
+ savedFormsCount: persistence.savedForms.length,
2276
+ onTitleChange: doc.updateTitle,
2277
+ onLanguageChange: setLanguage,
2278
+ onModeChange: setMode,
2279
+ onOpenSettings: () => setShowSettings(true),
2280
+ onNewForm: persistence.newForm,
2281
+ onOpenLibrary: () => setShowLibrary(true),
2282
+ onSaveExisting: persistence.saveExisting,
2283
+ onOpenJson: () => setShowJson(true),
2284
+ updateThemeColor,
2285
+ updateThemeLayout,
2286
+ resetTheme
2287
+ }
2288
+ ),
2289
+ persistence.loadingDraft ? /* @__PURE__ */ jsxs16("div", { style: styles.loadingScreen, children: [
2290
+ /* @__PURE__ */ jsx17(Loader22, { size: 20, className: "spin" }),
2291
+ /* @__PURE__ */ jsx17("span", { style: { marginTop: 8, fontSize: 13, color: "var(--fb-muted)" }, children: chrome.loadingDraft })
2292
+ ] }) : mode === "build" ? /* @__PURE__ */ jsxs16("div", { style: styles.workArea, children: [
2293
+ /* @__PURE__ */ jsx17(Palette, { activeSectionLabel, chrome, onAddField: doc.addField }),
2294
+ /* @__PURE__ */ jsx17(
2295
+ Canvas,
2296
+ {
2297
+ sections: doc.sections,
2298
+ activeSectionId: doc.activeSectionId,
2299
+ submitMode: doc.submitMode,
2300
+ submitStyle: doc.submitStyle,
2301
+ selectedId: doc.selectedId,
2302
+ dragOverKey: drag.dragOverKey,
2303
+ chrome,
2304
+ strings,
2305
+ language,
2306
+ onActivateSection: doc.setActiveSectionId,
2307
+ onToggleSectionCollapse: doc.toggleSectionCollapse,
2308
+ onUpdateSectionTitle: doc.updateSectionTitle,
2309
+ onUpdateSectionBackground: doc.updateSectionBackground,
2310
+ onDuplicateSection: doc.duplicateSection,
2311
+ onMoveSection: doc.moveSection,
2312
+ onDeleteSection: doc.deleteSection,
2313
+ onUpdateSectionSubmitStyle: doc.updateSectionSubmitStyle,
2314
+ onClearSectionSubmitStyle: doc.clearSectionSubmitStyle,
2315
+ onAddSection: doc.addSection,
2316
+ onSelectField: (sectionId, fieldId) => {
2317
+ doc.setSelectedId(fieldId);
2318
+ doc.setActiveSectionId(sectionId);
2319
+ },
2320
+ onFieldChange: doc.updateField,
2321
+ onMoveField: doc.moveField,
2322
+ onDuplicateField: doc.duplicateField,
2323
+ onDeleteField: doc.deleteField,
2324
+ getDropZoneHandlers: drag.getDropZoneHandlers,
2325
+ getDragHandleProps: drag.getDragHandleProps
2326
+ }
2327
+ ),
2328
+ /* @__PURE__ */ jsx17(
2329
+ Inspector,
2330
+ {
2331
+ selected: doc.selected,
2332
+ language,
2333
+ chrome,
2334
+ onUpdateField: (patch) => doc.selected && doc.updateField(doc.selected.id, patch),
2335
+ onDeleteField: () => doc.selected && doc.deleteField(doc.selected.id),
2336
+ onUpdateOption: (optIdx, patch) => doc.selected && doc.updateOption(doc.selected.id, optIdx, patch),
2337
+ onAddOption: () => doc.selected && doc.addOption(doc.selected.id),
2338
+ onRemoveOption: (optIdx) => doc.selected && doc.removeOption(doc.selected.id, optIdx)
2339
+ }
2340
+ )
2341
+ ] }) : /* @__PURE__ */ jsx17(
2342
+ PreviewPane,
2343
+ {
2344
+ title: doc.title,
2345
+ sections: doc.sections,
2346
+ onFieldChange: doc.updateField,
2347
+ language,
2348
+ strings,
2349
+ chrome,
2350
+ baseMaxWidth: theme.layout.maxWidth,
2351
+ submitLabel: doc.submitLabel,
2352
+ submitMode: doc.submitMode,
2353
+ submitStyle: doc.submitStyle
2354
+ }
2355
+ ),
2356
+ showJson && /* @__PURE__ */ jsx17(JsonModal, { chrome, jsonString, copied, onCopy: copyJson, onClose: () => setShowJson(false) }),
2357
+ showLibrary && /* @__PURE__ */ jsx17(
2358
+ LibraryModal,
2359
+ {
2360
+ chrome,
2361
+ savedForms: persistence.savedForms,
2362
+ currentFormId: persistence.currentFormId,
2363
+ onOpen: async (id) => {
2364
+ await persistence.loadForm(id);
2365
+ setShowLibrary(false);
2366
+ },
2367
+ onDelete: persistence.deleteForm,
2368
+ onClose: () => setShowLibrary(false)
2369
+ }
2370
+ ),
2371
+ persistence.saveAsPrompt && /* @__PURE__ */ jsx17(
2372
+ SaveAsModal,
2373
+ {
2374
+ chrome,
2375
+ suggestedName: persistence.saveAsPrompt.suggestedName,
2376
+ onSave: persistence.saveAs,
2377
+ onClose: persistence.dismissSaveAsPrompt
2378
+ }
2379
+ ),
2380
+ showSettings && /* @__PURE__ */ jsx17(
2381
+ SettingsModal,
2382
+ {
2383
+ chrome,
2384
+ strings,
2385
+ submitLabel: doc.submitLabel,
2386
+ language,
2387
+ submitMode: doc.submitMode,
2388
+ submitStyle: doc.submitStyle,
2389
+ onSubmitLabelChange: doc.updateSubmitLabel,
2390
+ onSubmitModeChange: doc.setSubmitMode,
2391
+ onSubmitStyleChange: doc.updateSubmitStyle,
2392
+ onClose: () => setShowSettings(false)
2393
+ }
2394
+ )
2395
+ ] });
2396
+ }
2397
+ export {
2398
+ FormBuilder,
2399
+ FormBuilder as default,
2400
+ localStorageAdapter
2401
+ };
2402
+ //# sourceMappingURL=index.js.map