form-page-builder 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -16
- package/dist/index.cjs +561 -193
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +94 -16
- package/dist/index.d.ts +94 -16
- package/dist/index.js +568 -207
- package/dist/index.js.map +1 -1
- package/package.json +12 -2
package/dist/index.cjs
CHANGED
|
@@ -21,17 +21,74 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
// src/index.ts
|
|
22
22
|
var index_exports = {};
|
|
23
23
|
__export(index_exports, {
|
|
24
|
+
CLIPBOARD_KEY: () => CLIPBOARD_KEY,
|
|
24
25
|
DARK_THEME: () => DARK_THEME,
|
|
25
26
|
DEFAULT_THEME: () => DEFAULT_THEME,
|
|
27
|
+
DRAFT_KEY: () => DRAFT_KEY,
|
|
26
28
|
FormBuilder: () => FormBuilder_default,
|
|
29
|
+
INDEX_KEY: () => INDEX_KEY,
|
|
27
30
|
default: () => FormBuilder_default,
|
|
28
|
-
|
|
31
|
+
formKey: () => formKey,
|
|
32
|
+
localStorageAdapter: () => localStorageAdapter,
|
|
33
|
+
parseTemplate: () => parseTemplate,
|
|
34
|
+
savedFormId: () => savedFormId,
|
|
35
|
+
serializeTemplate: () => serializeTemplate
|
|
29
36
|
});
|
|
30
37
|
module.exports = __toCommonJS(index_exports);
|
|
31
38
|
|
|
32
39
|
// src/FormBuilder.tsx
|
|
33
|
-
var
|
|
34
|
-
var
|
|
40
|
+
var import_react10 = require("react");
|
|
41
|
+
var import_lucide_react19 = require("lucide-react");
|
|
42
|
+
|
|
43
|
+
// src/constants/fieldTypes.ts
|
|
44
|
+
var import_lucide_react = require("lucide-react");
|
|
45
|
+
var FIELD_TYPES = [
|
|
46
|
+
{ type: "paragraph", icon: import_lucide_react.FileText, isContent: true, defaultIcon: "FileText" },
|
|
47
|
+
{ type: "image", icon: import_lucide_react.Image, isContent: true, isImage: true, defaultIcon: "Type" },
|
|
48
|
+
{ type: "button", icon: import_lucide_react.MousePointerClick, isContent: true, defaultIcon: "Type" },
|
|
49
|
+
{ type: "input", icon: import_lucide_react.Type, placeholder: true, hasSubtype: true, defaultIcon: "Type" },
|
|
50
|
+
{ type: "textarea", icon: import_lucide_react.AlignLeft, placeholder: true, defaultIcon: "AlignLeft" },
|
|
51
|
+
{ type: "select", icon: import_lucide_react.ChevronDown, options: true, defaultIcon: "ChevronDown" },
|
|
52
|
+
{ type: "radio", icon: import_lucide_react.Circle, options: true, defaultIcon: "Circle" },
|
|
53
|
+
{ type: "checkboxGroup", icon: import_lucide_react.ListChecks, options: true, multiValue: true, defaultIcon: "ListChecks" },
|
|
54
|
+
{ type: "checkbox", icon: import_lucide_react.CheckSquare, boolean: true, defaultIcon: "CheckSquare" },
|
|
55
|
+
{ type: "toggle", icon: import_lucide_react.ToggleLeft, boolean: true, defaultIcon: "ToggleLeft" }
|
|
56
|
+
];
|
|
57
|
+
var TYPE_MAP = Object.fromEntries(FIELD_TYPES.map((f) => [f.type, f]));
|
|
58
|
+
var CONTENT_TYPES = FIELD_TYPES.filter((f) => f.isContent);
|
|
59
|
+
var FORM_TYPES = FIELD_TYPES.filter((f) => !f.isContent);
|
|
60
|
+
function getMeta(type) {
|
|
61
|
+
return TYPE_MAP[type] || TYPE_MAP.input;
|
|
62
|
+
}
|
|
63
|
+
var LEGACY_INPUT_TYPES = ["text", "email", "phone", "number", "password", "date"];
|
|
64
|
+
var INPUT_SUBTYPES = ["text", "email", "phone", "number", "password", "date", "time"];
|
|
65
|
+
var IMAGE_SHAPES = ["square", "circle", "banner"];
|
|
66
|
+
var FIELD_TYPE_CHROME_KEY = {
|
|
67
|
+
paragraph: "fieldTypeParagraph",
|
|
68
|
+
image: "fieldTypeImage",
|
|
69
|
+
button: "fieldTypeButton",
|
|
70
|
+
input: "fieldTypeInput",
|
|
71
|
+
textarea: "fieldTypeTextarea",
|
|
72
|
+
select: "fieldTypeSelect",
|
|
73
|
+
radio: "fieldTypeRadio",
|
|
74
|
+
checkboxGroup: "fieldTypeCheckboxGroup",
|
|
75
|
+
checkbox: "fieldTypeCheckbox",
|
|
76
|
+
toggle: "fieldTypeToggle"
|
|
77
|
+
};
|
|
78
|
+
var INPUT_SUBTYPE_CHROME_KEY = {
|
|
79
|
+
text: "subtypeText",
|
|
80
|
+
email: "subtypeEmail",
|
|
81
|
+
phone: "subtypePhone",
|
|
82
|
+
number: "subtypeNumber",
|
|
83
|
+
password: "subtypePassword",
|
|
84
|
+
date: "subtypeDate",
|
|
85
|
+
time: "subtypeTime"
|
|
86
|
+
};
|
|
87
|
+
var IMAGE_SHAPE_CHROME_KEY = {
|
|
88
|
+
square: "shapeSquare",
|
|
89
|
+
circle: "shapeCircle",
|
|
90
|
+
banner: "shapeBanner"
|
|
91
|
+
};
|
|
35
92
|
|
|
36
93
|
// src/i18n/languages.ts
|
|
37
94
|
var DEFAULT_LANGUAGES = [
|
|
@@ -72,6 +129,7 @@ var DEFAULT_STRINGS = {
|
|
|
72
129
|
tooLarge: (n) => `${n}\u4EE5\u4E0B\u306E\u5024\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
|
|
73
130
|
}
|
|
74
131
|
};
|
|
132
|
+
DEFAULT_STRINGS.ja;
|
|
75
133
|
|
|
76
134
|
// src/i18n/chrome.ts
|
|
77
135
|
var CHROME = {
|
|
@@ -147,16 +205,33 @@ var CHROME = {
|
|
|
147
205
|
formJson: "Form JSON",
|
|
148
206
|
copy: "Copy",
|
|
149
207
|
copied: "Copied",
|
|
208
|
+
copyTemplate: "Copy template",
|
|
209
|
+
pasteTemplate: "Paste template",
|
|
210
|
+
copyTemplateTitle: "Copy this form as a template you can paste into another builder",
|
|
211
|
+
pasteTemplateTitle: "Replace this form with the copied template",
|
|
212
|
+
pasteTemplateConfirm: "Replace the current form with the copied template? This can't be undone.",
|
|
150
213
|
noTemplates: 'No templates saved yet. Click "Save" in the toolbar to store this form here.',
|
|
214
|
+
noTemplatesReadonly: "No templates available yet.",
|
|
151
215
|
updated: "Updated",
|
|
152
216
|
open: "Open",
|
|
217
|
+
applyTemplate: "Apply",
|
|
153
218
|
saveForm: "Save as template",
|
|
154
219
|
formName: "Template name",
|
|
155
220
|
saveToLibrary: "Save template",
|
|
156
221
|
loadingDraft: "Loading your draft...",
|
|
157
|
-
templatesLimitReached:
|
|
222
|
+
templatesLimitReached: (max) => `You can save up to ${max} template${max === 1 ? "" : "s"}. Delete one to save another.`,
|
|
223
|
+
templatesLimitTitle: "Template limit reached",
|
|
224
|
+
confirm: "Confirm",
|
|
225
|
+
cancel: "Cancel",
|
|
226
|
+
dismiss: "OK",
|
|
227
|
+
confirmReplace: "Replace",
|
|
158
228
|
current: "current",
|
|
159
229
|
customColor: "Custom color",
|
|
230
|
+
templateLabel: (name) => `Template: ${name}`,
|
|
231
|
+
templateEdited: "Edited",
|
|
232
|
+
templateLoading: "Loading template...",
|
|
233
|
+
templateLoadFailed: "Couldn't load that template.",
|
|
234
|
+
templateSaveFailed: "Couldn't save the template.",
|
|
160
235
|
fieldsCount: (n) => `${n} field${n === 1 ? "" : "s"}`,
|
|
161
236
|
fieldTypeInput: "Input",
|
|
162
237
|
fieldTypeTextarea: "Textarea",
|
|
@@ -283,16 +358,33 @@ var CHROME = {
|
|
|
283
358
|
formJson: "\u30D5\u30A9\u30FC\u30E0JSON",
|
|
284
359
|
copy: "\u30B3\u30D4\u30FC",
|
|
285
360
|
copied: "\u30B3\u30D4\u30FC\u3057\u307E\u3057\u305F",
|
|
361
|
+
copyTemplate: "\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u3092\u30B3\u30D4\u30FC",
|
|
362
|
+
pasteTemplate: "\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u3092\u8CBC\u308A\u4ED8\u3051",
|
|
363
|
+
copyTemplateTitle: "\u3053\u306E\u30D5\u30A9\u30FC\u30E0\u3092\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u3068\u3057\u3066\u30B3\u30D4\u30FC\u3057\u3001\u4ED6\u306E\u30D3\u30EB\u30C0\u30FC\u306B\u8CBC\u308A\u4ED8\u3051\u307E\u3059",
|
|
364
|
+
pasteTemplateTitle: "\u30B3\u30D4\u30FC\u3057\u305F\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u3067\u3053\u306E\u30D5\u30A9\u30FC\u30E0\u3092\u7F6E\u304D\u63DB\u3048\u307E\u3059",
|
|
365
|
+
pasteTemplateConfirm: "\u73FE\u5728\u306E\u30D5\u30A9\u30FC\u30E0\u3092\u30B3\u30D4\u30FC\u3057\u305F\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u3067\u7F6E\u304D\u63DB\u3048\u307E\u3059\u304B\uFF1F\u3053\u306E\u64CD\u4F5C\u306F\u53D6\u308A\u6D88\u305B\u307E\u305B\u3093\u3002",
|
|
286
366
|
noTemplates: "\u307E\u3060\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u304C\u3042\u308A\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",
|
|
367
|
+
noTemplatesReadonly: "\u5229\u7528\u3067\u304D\u308B\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u304C\u307E\u3060\u3042\u308A\u307E\u305B\u3093\u3002",
|
|
287
368
|
updated: "\u66F4\u65B0\u65E5\u6642",
|
|
288
369
|
open: "\u958B\u304F",
|
|
370
|
+
applyTemplate: "\u9069\u7528",
|
|
289
371
|
saveForm: "\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u3068\u3057\u3066\u4FDD\u5B58",
|
|
290
372
|
formName: "\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u540D",
|
|
291
373
|
saveToLibrary: "\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u3092\u4FDD\u5B58",
|
|
292
374
|
loadingDraft: "\u4E0B\u66F8\u304D\u3092\u8AAD\u307F\u8FBC\u3093\u3067\u3044\u307E\u3059...",
|
|
293
|
-
templatesLimitReached:
|
|
375
|
+
templatesLimitReached: (max) => `\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u306F${max}\u4EF6\u307E\u3067\u4FDD\u5B58\u3067\u304D\u307E\u3059\u30021\u4EF6\u524A\u9664\u3057\u3066\u304B\u3089\u4FDD\u5B58\u3057\u3066\u304F\u3060\u3055\u3044\u3002`,
|
|
376
|
+
templatesLimitTitle: "\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u306E\u4E0A\u9650\u306B\u9054\u3057\u307E\u3057\u305F",
|
|
377
|
+
confirm: "\u78BA\u5B9A",
|
|
378
|
+
cancel: "\u30AD\u30E3\u30F3\u30BB\u30EB",
|
|
379
|
+
dismiss: "OK",
|
|
380
|
+
confirmReplace: "\u7F6E\u304D\u63DB\u3048\u308B",
|
|
294
381
|
current: "\u73FE\u5728\u7DE8\u96C6\u4E2D",
|
|
295
382
|
customColor: "\u30AB\u30B9\u30BF\u30E0\u30AB\u30E9\u30FC",
|
|
383
|
+
templateLabel: (name) => `\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8: ${name}`,
|
|
384
|
+
templateEdited: "\u7DE8\u96C6\u6E08\u307F",
|
|
385
|
+
templateLoading: "\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u3092\u8AAD\u307F\u8FBC\u307F\u4E2D...",
|
|
386
|
+
templateLoadFailed: "\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u3092\u8AAD\u307F\u8FBC\u3081\u307E\u305B\u3093\u3067\u3057\u305F\u3002",
|
|
387
|
+
templateSaveFailed: "\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u3092\u4FDD\u5B58\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002",
|
|
296
388
|
fieldsCount: (n) => `${n}\u4EF6\u306E\u30D5\u30A3\u30FC\u30EB\u30C9`,
|
|
297
389
|
fieldTypeInput: "\u5165\u529B",
|
|
298
390
|
fieldTypeTextarea: "\u30C6\u30AD\u30B9\u30C8\u30A8\u30EA\u30A2",
|
|
@@ -348,6 +440,7 @@ var CHROME = {
|
|
|
348
440
|
spacingTicketPadding: "\u30D5\u30A3\u30FC\u30EB\u30C9\u5185\u4F59\u767D"
|
|
349
441
|
}
|
|
350
442
|
};
|
|
443
|
+
CHROME.ja;
|
|
351
444
|
|
|
352
445
|
// src/lib/bilingual.ts
|
|
353
446
|
function bi(en = "", ja = "") {
|
|
@@ -420,56 +513,6 @@ var localStorageAdapter = {
|
|
|
420
513
|
}
|
|
421
514
|
};
|
|
422
515
|
|
|
423
|
-
// src/constants/fieldTypes.ts
|
|
424
|
-
var import_lucide_react = require("lucide-react");
|
|
425
|
-
var FIELD_TYPES = [
|
|
426
|
-
{ type: "paragraph", icon: import_lucide_react.FileText, isContent: true, defaultIcon: "FileText" },
|
|
427
|
-
{ type: "image", icon: import_lucide_react.Image, isContent: true, isImage: true, defaultIcon: "Type" },
|
|
428
|
-
{ type: "button", icon: import_lucide_react.MousePointerClick, isContent: true, defaultIcon: "Type" },
|
|
429
|
-
{ type: "input", icon: import_lucide_react.Type, placeholder: true, hasSubtype: true, defaultIcon: "Type" },
|
|
430
|
-
{ type: "textarea", icon: import_lucide_react.AlignLeft, placeholder: true, defaultIcon: "AlignLeft" },
|
|
431
|
-
{ type: "select", icon: import_lucide_react.ChevronDown, options: true, defaultIcon: "ChevronDown" },
|
|
432
|
-
{ type: "radio", icon: import_lucide_react.Circle, options: true, defaultIcon: "Circle" },
|
|
433
|
-
{ type: "checkboxGroup", icon: import_lucide_react.ListChecks, options: true, multiValue: true, defaultIcon: "ListChecks" },
|
|
434
|
-
{ type: "checkbox", icon: import_lucide_react.CheckSquare, boolean: true, defaultIcon: "CheckSquare" },
|
|
435
|
-
{ type: "toggle", icon: import_lucide_react.ToggleLeft, boolean: true, defaultIcon: "ToggleLeft" }
|
|
436
|
-
];
|
|
437
|
-
var TYPE_MAP = Object.fromEntries(FIELD_TYPES.map((f) => [f.type, f]));
|
|
438
|
-
var CONTENT_TYPES = FIELD_TYPES.filter((f) => f.isContent);
|
|
439
|
-
var FORM_TYPES = FIELD_TYPES.filter((f) => !f.isContent);
|
|
440
|
-
function getMeta(type) {
|
|
441
|
-
return TYPE_MAP[type] || TYPE_MAP.input;
|
|
442
|
-
}
|
|
443
|
-
var LEGACY_INPUT_TYPES = ["text", "email", "phone", "number", "password", "date"];
|
|
444
|
-
var INPUT_SUBTYPES = ["text", "email", "phone", "number", "password", "date", "time"];
|
|
445
|
-
var IMAGE_SHAPES = ["square", "circle", "banner"];
|
|
446
|
-
var FIELD_TYPE_CHROME_KEY = {
|
|
447
|
-
paragraph: "fieldTypeParagraph",
|
|
448
|
-
image: "fieldTypeImage",
|
|
449
|
-
button: "fieldTypeButton",
|
|
450
|
-
input: "fieldTypeInput",
|
|
451
|
-
textarea: "fieldTypeTextarea",
|
|
452
|
-
select: "fieldTypeSelect",
|
|
453
|
-
radio: "fieldTypeRadio",
|
|
454
|
-
checkboxGroup: "fieldTypeCheckboxGroup",
|
|
455
|
-
checkbox: "fieldTypeCheckbox",
|
|
456
|
-
toggle: "fieldTypeToggle"
|
|
457
|
-
};
|
|
458
|
-
var INPUT_SUBTYPE_CHROME_KEY = {
|
|
459
|
-
text: "subtypeText",
|
|
460
|
-
email: "subtypeEmail",
|
|
461
|
-
phone: "subtypePhone",
|
|
462
|
-
number: "subtypeNumber",
|
|
463
|
-
password: "subtypePassword",
|
|
464
|
-
date: "subtypeDate",
|
|
465
|
-
time: "subtypeTime"
|
|
466
|
-
};
|
|
467
|
-
var IMAGE_SHAPE_CHROME_KEY = {
|
|
468
|
-
square: "shapeSquare",
|
|
469
|
-
circle: "shapeCircle",
|
|
470
|
-
banner: "shapeBanner"
|
|
471
|
-
};
|
|
472
|
-
|
|
473
516
|
// src/lib/id.ts
|
|
474
517
|
var genFormId = () => `form_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 7)}`;
|
|
475
518
|
var genSectionId = () => `section_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 7)}`;
|
|
@@ -633,10 +676,32 @@ function migrateDocument(raw) {
|
|
|
633
676
|
return { title, themeOverrides, sections };
|
|
634
677
|
}
|
|
635
678
|
|
|
679
|
+
// src/lib/template.ts
|
|
680
|
+
var TEMPLATE_FORMAT = 1;
|
|
681
|
+
function serializeTemplate(document2) {
|
|
682
|
+
const envelope = { __fpb: "template", v: TEMPLATE_FORMAT, document: document2 };
|
|
683
|
+
return JSON.stringify(envelope);
|
|
684
|
+
}
|
|
685
|
+
function parseTemplate(raw) {
|
|
686
|
+
if (!raw) return null;
|
|
687
|
+
let parsed;
|
|
688
|
+
try {
|
|
689
|
+
parsed = JSON.parse(raw);
|
|
690
|
+
} catch {
|
|
691
|
+
return null;
|
|
692
|
+
}
|
|
693
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
694
|
+
const obj = parsed;
|
|
695
|
+
const candidate = obj.__fpb === "template" && obj.document ? obj.document : obj;
|
|
696
|
+
return migrateDocument(candidate);
|
|
697
|
+
}
|
|
698
|
+
|
|
636
699
|
// src/lib/features.ts
|
|
700
|
+
var DEFAULT_MAX_TEMPLATES = 5;
|
|
637
701
|
var DEFAULT_FEATURES = {
|
|
638
702
|
naming: true,
|
|
639
|
-
templates: true,
|
|
703
|
+
templates: { enabled: true, manage: true, max: DEFAULT_MAX_TEMPLATES },
|
|
704
|
+
templateClipboard: true,
|
|
640
705
|
newForm: true,
|
|
641
706
|
autosave: true,
|
|
642
707
|
jsonView: true,
|
|
@@ -647,14 +712,24 @@ var DEFAULT_FEATURES = {
|
|
|
647
712
|
contentBlocks: true,
|
|
648
713
|
fieldTypes: true,
|
|
649
714
|
sections: true,
|
|
650
|
-
|
|
715
|
+
sectionBackground: true,
|
|
716
|
+
dragReorder: true,
|
|
717
|
+
deviceToggle: true
|
|
651
718
|
};
|
|
719
|
+
function resolveTemplates(t2) {
|
|
720
|
+
if (t2 === false) return { enabled: false, manage: false, max: DEFAULT_MAX_TEMPLATES };
|
|
721
|
+
if (t2 === void 0 || t2 === true) return { enabled: true, manage: true, max: DEFAULT_MAX_TEMPLATES };
|
|
722
|
+
return { enabled: true, manage: t2.manage ?? true, max: t2.max ?? DEFAULT_MAX_TEMPLATES };
|
|
723
|
+
}
|
|
652
724
|
function resolveFeatures(features) {
|
|
653
725
|
return {
|
|
654
726
|
...DEFAULT_FEATURES,
|
|
655
727
|
...features,
|
|
728
|
+
templates: resolveTemplates(features?.templates),
|
|
656
729
|
contentBlocks: Array.isArray(features?.contentBlocks) ? new Set(features.contentBlocks) : features?.contentBlocks ?? DEFAULT_FEATURES.contentBlocks,
|
|
657
|
-
fieldTypes: Array.isArray(features?.fieldTypes) ? new Set(features.fieldTypes) : features?.fieldTypes ?? DEFAULT_FEATURES.fieldTypes
|
|
730
|
+
fieldTypes: Array.isArray(features?.fieldTypes) ? new Set(features.fieldTypes) : features?.fieldTypes ?? DEFAULT_FEATURES.fieldTypes,
|
|
731
|
+
// Unset → follow `sections`, so an existing `sections: false` still hides the picker.
|
|
732
|
+
sectionBackground: features?.sectionBackground ?? features?.sections ?? DEFAULT_FEATURES.sectionBackground
|
|
658
733
|
};
|
|
659
734
|
}
|
|
660
735
|
function isContentBlockEnabled(features, type) {
|
|
@@ -684,17 +759,17 @@ var DEFAULT_THEME = {
|
|
|
684
759
|
|
|
685
760
|
// src/hooks/useTheme.ts
|
|
686
761
|
function useTheme(themeOverrideProp) {
|
|
687
|
-
const
|
|
762
|
+
const [themeOverrides, setThemeOverrides] = (0, import_react.useState)({});
|
|
763
|
+
const baseTheme = (0, import_react.useMemo)(() => ({
|
|
688
764
|
...DEFAULT_THEME,
|
|
689
765
|
...themeOverrideProp || {},
|
|
690
766
|
layout: { ...DEFAULT_THEME.layout, ...themeOverrideProp && themeOverrideProp.layout || {} }
|
|
691
|
-
};
|
|
692
|
-
const
|
|
693
|
-
const theme = {
|
|
767
|
+
}), [themeOverrideProp]);
|
|
768
|
+
const theme = (0, import_react.useMemo)(() => ({
|
|
694
769
|
...baseTheme,
|
|
695
770
|
...themeOverrides,
|
|
696
771
|
layout: { ...baseTheme.layout, ...themeOverrides.layout || {} }
|
|
697
|
-
};
|
|
772
|
+
}), [baseTheme, themeOverrides]);
|
|
698
773
|
function updateThemeColor(key, value) {
|
|
699
774
|
setThemeOverrides((prev) => ({ ...prev, [key]: value }));
|
|
700
775
|
}
|
|
@@ -893,35 +968,49 @@ var import_react3 = require("react");
|
|
|
893
968
|
var DRAFT_KEY = "form-page-builder:draft";
|
|
894
969
|
var INDEX_KEY = "form-page-builder:index";
|
|
895
970
|
var formKey = (id) => `form-page-builder:saved:${id}`;
|
|
971
|
+
var savedFormId = (key) => key.startsWith("form-page-builder:saved:") ? key.slice("form-page-builder:saved:".length) : null;
|
|
972
|
+
var CLIPBOARD_KEY = "form-page-builder:clipboard";
|
|
896
973
|
|
|
897
974
|
// src/hooks/usePersistence.ts
|
|
898
|
-
|
|
975
|
+
function snapshotOf(d) {
|
|
976
|
+
return JSON.stringify({ title: d.title, themeOverrides: d.themeOverrides, sections: d.sections });
|
|
977
|
+
}
|
|
899
978
|
function usePersistence({
|
|
900
979
|
storage,
|
|
901
980
|
autosave,
|
|
981
|
+
templateMax,
|
|
982
|
+
templateManage,
|
|
902
983
|
language,
|
|
903
984
|
chrome,
|
|
904
|
-
document,
|
|
985
|
+
document: document2,
|
|
905
986
|
initialDocument,
|
|
906
987
|
onLoadDocument,
|
|
907
988
|
onLoadThemeOverrides,
|
|
908
989
|
onTitleChange,
|
|
909
990
|
onNewForm,
|
|
991
|
+
onTemplateChange,
|
|
910
992
|
ensureActiveSection
|
|
911
993
|
}) {
|
|
912
994
|
const [currentFormId, setCurrentFormId] = (0, import_react3.useState)(null);
|
|
913
995
|
const [loadingDraft, setLoadingDraft] = (0, import_react3.useState)(true);
|
|
914
996
|
const [saveState, setSaveState] = (0, import_react3.useState)("idle");
|
|
997
|
+
const [templateState, setTemplateState] = (0, import_react3.useState)("idle");
|
|
915
998
|
const [savedForms, setSavedForms] = (0, import_react3.useState)([]);
|
|
916
999
|
const [saveAsPrompt, setSaveAsPrompt] = (0, import_react3.useState)(null);
|
|
1000
|
+
const [notice, setNotice] = (0, import_react3.useState)(null);
|
|
917
1001
|
const autosaveTimer = (0, import_react3.useRef)(null);
|
|
918
1002
|
const hasLoadedOnce = (0, import_react3.useRef)(false);
|
|
1003
|
+
const savedSnapshot = (0, import_react3.useRef)(null);
|
|
1004
|
+
function flashSaved() {
|
|
1005
|
+
setTemplateState("saved");
|
|
1006
|
+
setTimeout(() => setTemplateState((s) => s === "saved" ? "idle" : s), 1500);
|
|
1007
|
+
}
|
|
919
1008
|
async function refreshLibrary() {
|
|
920
1009
|
try {
|
|
921
1010
|
const raw = await storage.get(INDEX_KEY);
|
|
922
1011
|
const list = raw ? JSON.parse(raw) : [];
|
|
923
1012
|
setSavedForms(list.sort((a, b) => b.updatedAt - a.updatedAt));
|
|
924
|
-
} catch
|
|
1013
|
+
} catch {
|
|
925
1014
|
setSavedForms([]);
|
|
926
1015
|
}
|
|
927
1016
|
}
|
|
@@ -945,12 +1034,21 @@ function usePersistence({
|
|
|
945
1034
|
onLoadDocument(doc);
|
|
946
1035
|
onLoadThemeOverrides(doc.themeOverrides);
|
|
947
1036
|
}
|
|
948
|
-
if (parsed.currentFormId)
|
|
1037
|
+
if (parsed.currentFormId && templateManage) {
|
|
1038
|
+
setCurrentFormId(parsed.currentFormId);
|
|
1039
|
+
try {
|
|
1040
|
+
const tplRaw = await storage.get(formKey(parsed.currentFormId));
|
|
1041
|
+
const tplDoc = tplRaw ? migrateDocument(JSON.parse(tplRaw)) : null;
|
|
1042
|
+
savedSnapshot.current = tplDoc ? snapshotOf(tplDoc) : null;
|
|
1043
|
+
} catch {
|
|
1044
|
+
savedSnapshot.current = null;
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
949
1047
|
} else {
|
|
950
1048
|
ensureActiveSection();
|
|
951
1049
|
}
|
|
952
1050
|
}
|
|
953
|
-
} catch
|
|
1051
|
+
} catch {
|
|
954
1052
|
ensureActiveSection();
|
|
955
1053
|
} finally {
|
|
956
1054
|
setLoadingDraft(false);
|
|
@@ -965,86 +1063,125 @@ function usePersistence({
|
|
|
965
1063
|
if (autosaveTimer.current) clearTimeout(autosaveTimer.current);
|
|
966
1064
|
autosaveTimer.current = setTimeout(async () => {
|
|
967
1065
|
try {
|
|
968
|
-
await storage.set(DRAFT_KEY, JSON.stringify({ ...
|
|
1066
|
+
await storage.set(DRAFT_KEY, JSON.stringify({ ...document2, currentFormId }));
|
|
969
1067
|
setSaveState("saved");
|
|
970
|
-
} catch
|
|
1068
|
+
} catch {
|
|
971
1069
|
setSaveState("error");
|
|
972
1070
|
}
|
|
973
1071
|
}, 600);
|
|
974
1072
|
return () => {
|
|
975
1073
|
if (autosaveTimer.current) clearTimeout(autosaveTimer.current);
|
|
976
1074
|
};
|
|
977
|
-
}, [
|
|
1075
|
+
}, [document2.title, document2.themeOverrides, document2.sections, currentFormId]);
|
|
978
1076
|
async function saveAs(name) {
|
|
979
|
-
if (
|
|
980
|
-
|
|
1077
|
+
if (!templateManage) return;
|
|
1078
|
+
if (savedForms.length >= templateMax) {
|
|
1079
|
+
setSaveAsPrompt(null);
|
|
1080
|
+
setNotice({ title: chrome.templatesLimitTitle, message: chrome.templatesLimitReached(templateMax) });
|
|
981
1081
|
return;
|
|
982
1082
|
}
|
|
983
1083
|
const id = genFormId();
|
|
984
1084
|
const now = Date.now();
|
|
1085
|
+
setTemplateState("saving");
|
|
985
1086
|
try {
|
|
986
1087
|
const newTitle = bi(name, "");
|
|
987
|
-
|
|
1088
|
+
const docToSave = { ...document2, title: newTitle };
|
|
1089
|
+
await storage.set(formKey(id), JSON.stringify({ ...docToSave, id, updatedAt: now }));
|
|
988
1090
|
const next = [...savedForms, { id, title: name, updatedAt: now }];
|
|
989
1091
|
await storage.set(INDEX_KEY, JSON.stringify(next));
|
|
990
1092
|
setSavedForms(next);
|
|
991
1093
|
setCurrentFormId(id);
|
|
992
1094
|
onTitleChange(newTitle);
|
|
993
1095
|
setSaveAsPrompt(null);
|
|
994
|
-
|
|
995
|
-
|
|
1096
|
+
savedSnapshot.current = snapshotOf(docToSave);
|
|
1097
|
+
flashSaved();
|
|
1098
|
+
onTemplateChange?.({ id, title: name, source: "new" });
|
|
1099
|
+
} catch {
|
|
1100
|
+
setTemplateState("error");
|
|
996
1101
|
}
|
|
997
1102
|
}
|
|
998
1103
|
async function saveExisting() {
|
|
1104
|
+
if (!templateManage) return;
|
|
999
1105
|
if (!currentFormId) {
|
|
1000
|
-
setSaveAsPrompt({ open: true, suggestedName: t(
|
|
1106
|
+
setSaveAsPrompt({ open: true, suggestedName: t(document2.title, language) });
|
|
1001
1107
|
return;
|
|
1002
1108
|
}
|
|
1003
1109
|
const now = Date.now();
|
|
1110
|
+
setTemplateState("saving");
|
|
1004
1111
|
try {
|
|
1005
|
-
await storage.set(formKey(currentFormId), JSON.stringify({ ...
|
|
1006
|
-
const next = savedForms.map((f) => f.id === currentFormId ? { ...f, title: t(
|
|
1112
|
+
await storage.set(formKey(currentFormId), JSON.stringify({ ...document2, id: currentFormId, updatedAt: now }));
|
|
1113
|
+
const next = savedForms.map((f) => f.id === currentFormId ? { ...f, title: t(document2.title, "en"), updatedAt: now } : f);
|
|
1007
1114
|
setSavedForms(next);
|
|
1008
1115
|
await storage.set(INDEX_KEY, JSON.stringify(next));
|
|
1009
|
-
|
|
1010
|
-
|
|
1116
|
+
savedSnapshot.current = snapshotOf(document2);
|
|
1117
|
+
flashSaved();
|
|
1118
|
+
onTemplateChange?.({ id: currentFormId, title: t(document2.title, language), source: "saved" });
|
|
1119
|
+
} catch {
|
|
1120
|
+
setTemplateState("error");
|
|
1011
1121
|
}
|
|
1012
1122
|
}
|
|
1013
1123
|
async function loadForm(id) {
|
|
1124
|
+
setTemplateState("loading");
|
|
1014
1125
|
try {
|
|
1015
1126
|
const raw = await storage.get(formKey(id));
|
|
1016
|
-
if (!raw)
|
|
1127
|
+
if (!raw) {
|
|
1128
|
+
setTemplateState("error");
|
|
1129
|
+
return false;
|
|
1130
|
+
}
|
|
1017
1131
|
const doc = migrateDocument(JSON.parse(raw));
|
|
1018
|
-
if (!doc)
|
|
1132
|
+
if (!doc) {
|
|
1133
|
+
setTemplateState("error");
|
|
1134
|
+
return false;
|
|
1135
|
+
}
|
|
1019
1136
|
onLoadDocument(doc);
|
|
1020
1137
|
onLoadThemeOverrides(doc.themeOverrides);
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1138
|
+
const boundId = templateManage ? id : null;
|
|
1139
|
+
setCurrentFormId(boundId);
|
|
1140
|
+
savedSnapshot.current = boundId ? snapshotOf(doc) : null;
|
|
1141
|
+
setTemplateState("idle");
|
|
1142
|
+
onTemplateChange?.({ id, title: t(doc.title, language), source: "applied" });
|
|
1143
|
+
return true;
|
|
1144
|
+
} catch {
|
|
1145
|
+
setTemplateState("error");
|
|
1146
|
+
return false;
|
|
1024
1147
|
}
|
|
1025
1148
|
}
|
|
1026
1149
|
async function deleteForm(id) {
|
|
1150
|
+
if (!templateManage) return;
|
|
1151
|
+
const removed = savedForms.find((f) => f.id === id);
|
|
1027
1152
|
try {
|
|
1028
1153
|
await storage.delete(formKey(id));
|
|
1029
1154
|
const next = savedForms.filter((f) => f.id !== id);
|
|
1030
1155
|
setSavedForms(next);
|
|
1031
1156
|
await storage.set(INDEX_KEY, JSON.stringify(next));
|
|
1032
|
-
if (currentFormId === id)
|
|
1033
|
-
|
|
1157
|
+
if (currentFormId === id) {
|
|
1158
|
+
setCurrentFormId(null);
|
|
1159
|
+
savedSnapshot.current = null;
|
|
1160
|
+
}
|
|
1161
|
+
onTemplateChange?.({ id: currentFormId === id ? null : id, title: removed?.title ?? "", source: "deleted" });
|
|
1162
|
+
} catch {
|
|
1163
|
+
setTemplateState("error");
|
|
1034
1164
|
}
|
|
1035
1165
|
}
|
|
1036
1166
|
function newForm() {
|
|
1037
1167
|
onNewForm();
|
|
1038
1168
|
setCurrentFormId(null);
|
|
1169
|
+
savedSnapshot.current = null;
|
|
1170
|
+
setTemplateState("idle");
|
|
1039
1171
|
}
|
|
1172
|
+
const activeTemplate = currentFormId ? savedForms.find((f) => f.id === currentFormId) ?? null : null;
|
|
1173
|
+
const isTemplateDirty = savedSnapshot.current !== null && snapshotOf(document2) !== savedSnapshot.current;
|
|
1040
1174
|
function dismissSaveAsPrompt() {
|
|
1041
1175
|
setSaveAsPrompt(null);
|
|
1042
1176
|
}
|
|
1043
1177
|
return {
|
|
1044
1178
|
loadingDraft,
|
|
1045
1179
|
saveState,
|
|
1180
|
+
templateState,
|
|
1046
1181
|
savedForms,
|
|
1047
1182
|
currentFormId,
|
|
1183
|
+
activeTemplateTitle: activeTemplate?.title ?? null,
|
|
1184
|
+
isTemplateDirty,
|
|
1048
1185
|
saveAs,
|
|
1049
1186
|
saveExisting,
|
|
1050
1187
|
loadForm,
|
|
@@ -1052,15 +1189,64 @@ function usePersistence({
|
|
|
1052
1189
|
refreshLibrary,
|
|
1053
1190
|
newForm,
|
|
1054
1191
|
saveAsPrompt,
|
|
1055
|
-
dismissSaveAsPrompt
|
|
1192
|
+
dismissSaveAsPrompt,
|
|
1193
|
+
notice,
|
|
1194
|
+
dismissNotice: () => setNotice(null)
|
|
1056
1195
|
};
|
|
1057
1196
|
}
|
|
1058
1197
|
|
|
1059
|
-
// src/hooks/
|
|
1198
|
+
// src/hooks/useTemplateClipboard.ts
|
|
1060
1199
|
var import_react4 = require("react");
|
|
1200
|
+
var sameTabListeners = /* @__PURE__ */ new Set();
|
|
1201
|
+
function notifySameTab() {
|
|
1202
|
+
sameTabListeners.forEach((fn) => fn());
|
|
1203
|
+
}
|
|
1204
|
+
function readKey(key) {
|
|
1205
|
+
try {
|
|
1206
|
+
return typeof window !== "undefined" && window.localStorage ? window.localStorage.getItem(key) : null;
|
|
1207
|
+
} catch {
|
|
1208
|
+
return null;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
function useTemplateClipboard(clipboardKey = CLIPBOARD_KEY) {
|
|
1212
|
+
const keyRef = (0, import_react4.useRef)(clipboardKey);
|
|
1213
|
+
keyRef.current = clipboardKey;
|
|
1214
|
+
const [hasClipboard, setHasClipboard] = (0, import_react4.useState)(false);
|
|
1215
|
+
const refresh = (0, import_react4.useCallback)(() => {
|
|
1216
|
+
setHasClipboard(parseTemplate(readKey(keyRef.current)) !== null);
|
|
1217
|
+
}, []);
|
|
1218
|
+
(0, import_react4.useEffect)(() => {
|
|
1219
|
+
refresh();
|
|
1220
|
+
const onStorage = (e) => {
|
|
1221
|
+
if (e.key === null || e.key === keyRef.current) refresh();
|
|
1222
|
+
};
|
|
1223
|
+
sameTabListeners.add(refresh);
|
|
1224
|
+
window.addEventListener("storage", onStorage);
|
|
1225
|
+
return () => {
|
|
1226
|
+
sameTabListeners.delete(refresh);
|
|
1227
|
+
window.removeEventListener("storage", onStorage);
|
|
1228
|
+
};
|
|
1229
|
+
}, [refresh]);
|
|
1230
|
+
const copyTemplate = (0, import_react4.useCallback)((document2) => {
|
|
1231
|
+
const str = serializeTemplate(document2);
|
|
1232
|
+
try {
|
|
1233
|
+
window.localStorage?.setItem(keyRef.current, str);
|
|
1234
|
+
} catch {
|
|
1235
|
+
}
|
|
1236
|
+
notifySameTab();
|
|
1237
|
+
refresh();
|
|
1238
|
+
}, [refresh]);
|
|
1239
|
+
const readTemplate = (0, import_react4.useCallback)(() => {
|
|
1240
|
+
return parseTemplate(readKey(keyRef.current));
|
|
1241
|
+
}, []);
|
|
1242
|
+
return { hasClipboard, copyTemplate, readTemplate };
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
// src/hooks/useDragReorder.ts
|
|
1246
|
+
var import_react5 = require("react");
|
|
1061
1247
|
function useDragReorder(reorderWithinSection) {
|
|
1062
|
-
const dragRef = (0,
|
|
1063
|
-
const [dragOverKey, setDragOverKey] = (0,
|
|
1248
|
+
const dragRef = (0, import_react5.useRef)({ sectionId: null, index: null });
|
|
1249
|
+
const [dragOverKey, setDragOverKey] = (0, import_react5.useState)(null);
|
|
1064
1250
|
function getDropZoneHandlers(sectionId, index) {
|
|
1065
1251
|
const dragKey = `${sectionId}:${index}`;
|
|
1066
1252
|
return {
|
|
@@ -1094,7 +1280,7 @@ var import_lucide_react2 = require("lucide-react");
|
|
|
1094
1280
|
var styles = {
|
|
1095
1281
|
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)", display: "flex", flexDirection: "column" },
|
|
1096
1282
|
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" },
|
|
1097
|
-
toolbarLeft: { display: "flex", alignItems: "center", gap: 10 },
|
|
1283
|
+
toolbarLeft: { display: "flex", alignItems: "center", gap: 10, minWidth: 0, flexShrink: 1 },
|
|
1098
1284
|
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" },
|
|
1099
1285
|
titleInput: { border: "none", outline: "none", fontSize: 14, fontWeight: 600, background: "transparent", color: "var(--fb-ink)", padding: "4px 6px", borderRadius: 6, minWidth: 160 },
|
|
1100
1286
|
toolbarRight: { display: "flex", alignItems: "center", gap: 6, flexWrap: "wrap" },
|
|
@@ -1104,6 +1290,8 @@ var styles = {
|
|
|
1104
1290
|
ghostBtn: { display: "flex", alignItems: "center", gap: 6, padding: "6px 12px", borderRadius: 7, border: "1px solid var(--fb-border)", background: "var(--fb-surface)", color: "var(--fb-ink)", fontSize: 13, fontWeight: 500 },
|
|
1105
1291
|
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 },
|
|
1106
1292
|
saveStatus: { display: "flex", alignItems: "center", gap: 5, fontSize: 12, color: "var(--fb-muted)", minWidth: 60 },
|
|
1293
|
+
templateTag: { display: "flex", alignItems: "center", gap: 6, fontSize: 12, color: "var(--fb-muted)", minWidth: 0, flexShrink: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
1294
|
+
toolbarIconBtn: { display: "flex", alignItems: "center", justifyContent: "center", padding: "6px 9px", borderRadius: 7, border: "1px solid var(--fb-border)", background: "var(--fb-surface)", color: "var(--fb-ink)" },
|
|
1107
1295
|
countBadge: { fontSize: 10.5, fontWeight: 700, background: "var(--fb-primary-soft)", color: "var(--fb-primary)", borderRadius: 9, padding: "1px 6px", marginLeft: 2 },
|
|
1108
1296
|
loadingScreen: { flex: 1, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", color: "var(--fb-muted)" },
|
|
1109
1297
|
libraryBody: { padding: "var(--fb-space-panel)", overflowY: "auto", display: "flex", flexDirection: "column", gap: 4 },
|
|
@@ -1240,21 +1428,36 @@ function Toolbar({
|
|
|
1240
1428
|
languages,
|
|
1241
1429
|
mode,
|
|
1242
1430
|
saveState,
|
|
1431
|
+
templateState,
|
|
1432
|
+
activeTemplateTitle,
|
|
1433
|
+
templateDirty,
|
|
1243
1434
|
chrome,
|
|
1244
1435
|
features,
|
|
1245
1436
|
savedFormsCount,
|
|
1437
|
+
clipboardReady,
|
|
1438
|
+
templateCopied,
|
|
1246
1439
|
onTitleChange,
|
|
1247
1440
|
onLanguageChange,
|
|
1248
1441
|
onModeChange,
|
|
1249
1442
|
onNewForm,
|
|
1250
1443
|
onOpenLibrary,
|
|
1251
1444
|
onSaveExisting,
|
|
1252
|
-
onOpenJson
|
|
1445
|
+
onOpenJson,
|
|
1446
|
+
onCopyTemplate,
|
|
1447
|
+
onPasteTemplate
|
|
1253
1448
|
}) {
|
|
1254
1449
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: styles.toolbar, children: [
|
|
1255
1450
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: styles.toolbarLeft, children: [
|
|
1256
1451
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: styles.logoMark, children: "FB" }),
|
|
1257
|
-
features.naming ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { value: t(title, language), onChange: (e) => onTitleChange(e.target.value), style: styles.titleInput, "aria-label": "Form title" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: styles.titleInput, children: t(title, language) })
|
|
1452
|
+
features.naming ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { value: t(title, language), onChange: (e) => onTitleChange(e.target.value), style: styles.titleInput, "aria-label": "Form title" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: styles.titleInput, children: t(title, language) }),
|
|
1453
|
+
templateState === "loading" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: styles.saveStatus, children: [
|
|
1454
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react2.Loader2, { size: 12, className: "spin" }),
|
|
1455
|
+
" ",
|
|
1456
|
+
chrome.templateLoading
|
|
1457
|
+
] }) : activeTemplateTitle && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "fb-template-tag", style: styles.templateTag, title: chrome.templateLabel(activeTemplateTitle), children: [
|
|
1458
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { overflow: "hidden", textOverflow: "ellipsis" }, children: chrome.templateLabel(activeTemplateTitle) }),
|
|
1459
|
+
templateDirty && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: styles.currentBadge, children: chrome.templateEdited })
|
|
1460
|
+
] })
|
|
1258
1461
|
] }),
|
|
1259
1462
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: styles.toolbarRight, children: [
|
|
1260
1463
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: styles.saveStatus, children: [
|
|
@@ -1287,20 +1490,20 @@ function Toolbar({
|
|
|
1287
1490
|
chrome.preview
|
|
1288
1491
|
] })
|
|
1289
1492
|
] }),
|
|
1290
|
-
(features.newForm || features.templates) && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: styles.toolbarDivider }),
|
|
1493
|
+
(features.newForm || features.templates.enabled) && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: styles.toolbarDivider }),
|
|
1291
1494
|
features.newForm && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("button", { style: styles.ghostBtn, onClick: onNewForm, title: chrome.startNewForm, children: [
|
|
1292
1495
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react2.FilePlus2, { size: 14 }),
|
|
1293
1496
|
" ",
|
|
1294
1497
|
chrome.newForm
|
|
1295
1498
|
] }),
|
|
1296
|
-
features.templates && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
1499
|
+
features.templates.enabled && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
1297
1500
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("button", { style: styles.ghostBtn, onClick: onOpenLibrary, title: chrome.openTemplatesTitle, children: [
|
|
1298
1501
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react2.FolderOpen, { size: 14 }),
|
|
1299
1502
|
" ",
|
|
1300
1503
|
chrome.templates,
|
|
1301
1504
|
savedFormsCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: styles.countBadge, children: savedFormsCount })
|
|
1302
1505
|
] }),
|
|
1303
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("button", { style: styles.primaryBtn, onClick: onSaveExisting, title: chrome.saveToLibraryTitle, children: [
|
|
1506
|
+
features.templates.manage && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("button", { style: styles.primaryBtn, onClick: onSaveExisting, title: chrome.saveToLibraryTitle, children: [
|
|
1304
1507
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react2.Save, { size: 14 }),
|
|
1305
1508
|
" ",
|
|
1306
1509
|
chrome.save
|
|
@@ -1310,13 +1513,17 @@ function Toolbar({
|
|
|
1310
1513
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react2.Code2, { size: 14 }),
|
|
1311
1514
|
" ",
|
|
1312
1515
|
chrome.viewJson
|
|
1516
|
+
] }),
|
|
1517
|
+
features.templateClipboard && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
1518
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { style: styles.toolbarIconBtn, onClick: onCopyTemplate, title: chrome.copyTemplateTitle, "aria-label": chrome.copyTemplate, children: templateCopied ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react2.Check, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react2.ClipboardCopy, { size: 14 }) }),
|
|
1519
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { style: styles.toolbarIconBtn, onClick: onPasteTemplate, disabled: !clipboardReady, title: chrome.pasteTemplateTitle, "aria-label": chrome.pasteTemplate, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react2.ClipboardPaste, { size: 14 }) })
|
|
1313
1520
|
] })
|
|
1314
1521
|
] })
|
|
1315
1522
|
] });
|
|
1316
1523
|
}
|
|
1317
1524
|
|
|
1318
1525
|
// src/components/Palette.tsx
|
|
1319
|
-
var
|
|
1526
|
+
var import_react6 = require("react");
|
|
1320
1527
|
var import_lucide_react5 = require("lucide-react");
|
|
1321
1528
|
|
|
1322
1529
|
// src/components/Segmented.tsx
|
|
@@ -1411,11 +1618,13 @@ function Palette({
|
|
|
1411
1618
|
theme,
|
|
1412
1619
|
updateThemeColor,
|
|
1413
1620
|
updateThemeLayout,
|
|
1414
|
-
resetTheme
|
|
1621
|
+
resetTheme,
|
|
1622
|
+
fieldCount
|
|
1415
1623
|
}) {
|
|
1416
|
-
const [tab, setTab] = (0,
|
|
1624
|
+
const [tab, setTab] = (0, import_react6.useState)("blocks");
|
|
1417
1625
|
const contentTypes = CONTENT_TYPES.filter((f) => isContentBlockEnabled(features, f.type));
|
|
1418
1626
|
const formTypes = FORM_TYPES.filter((f) => isFieldTypeEnabled(features, f.type));
|
|
1627
|
+
const atFieldCap = typeof features.maxFields === "number" && fieldCount >= features.maxFields;
|
|
1419
1628
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "fb-palette", style: styles.palette, children: [
|
|
1420
1629
|
features.design && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { marginBottom: 14 }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1421
1630
|
Segmented,
|
|
@@ -1458,7 +1667,8 @@ function Palette({
|
|
|
1458
1667
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { ...styles.panelHeading, marginTop: 16 }, children: chrome.formFields }),
|
|
1459
1668
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: styles.paletteList, children: formTypes.map((f) => {
|
|
1460
1669
|
const Icon = f.icon;
|
|
1461
|
-
|
|
1670
|
+
const itemStyle = atFieldCap ? { ...styles.paletteItem, opacity: 0.45, cursor: "not-allowed" } : styles.paletteItem;
|
|
1671
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("button", { type: "button", disabled: atFieldCap, style: itemStyle, onClick: () => onAddField(f.type), children: [
|
|
1462
1672
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Icon, { size: 16, color: "var(--fb-primary)" }),
|
|
1463
1673
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: chrome[FIELD_TYPE_CHROME_KEY[f.type]] }),
|
|
1464
1674
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react5.Plus, { size: 13, color: "var(--fb-muted)", style: { marginLeft: "auto" } })
|
|
@@ -1796,7 +2006,7 @@ function SectionCard({
|
|
|
1796
2006
|
),
|
|
1797
2007
|
section.collapsed && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: styles.miniBadge, children: chrome.fieldsCount(section.fields.length) }),
|
|
1798
2008
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: styles.sectionHeaderActions, onClick: (e) => e.stopPropagation(), children: [
|
|
1799
|
-
features.
|
|
2009
|
+
features.sectionBackground && !section.collapsed && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: styles.swatchRow, children: [
|
|
1800
2010
|
SECTION_BG_SWATCHES.map((c) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("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")),
|
|
1801
2011
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("input", { type: "color", title: chrome.customColor, value: /^#/.test(section.background) ? section.background : "#ffffff", onChange: (e) => onUpdateBackground(e.target.value), style: styles.colorPickerInput })
|
|
1802
2012
|
] }),
|
|
@@ -2117,7 +2327,7 @@ function InspectorBody({ selected, language, chrome, features, onUpdateField, on
|
|
|
2117
2327
|
}
|
|
2118
2328
|
|
|
2119
2329
|
// src/components/PreviewPane.tsx
|
|
2120
|
-
var
|
|
2330
|
+
var import_react7 = require("react");
|
|
2121
2331
|
var import_lucide_react14 = require("lucide-react");
|
|
2122
2332
|
|
|
2123
2333
|
// src/lib/validate.ts
|
|
@@ -2165,11 +2375,11 @@ function collectFieldValues(fields) {
|
|
|
2165
2375
|
|
|
2166
2376
|
// src/components/PreviewPane.tsx
|
|
2167
2377
|
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
2168
|
-
function PreviewPane({ title, sections, onFieldChange, language, strings, chrome, baseMaxWidth, onSubmit }) {
|
|
2169
|
-
const [device, setDevice] = (0,
|
|
2170
|
-
const [errors, setErrors] = (0,
|
|
2171
|
-
const [submitted, setSubmitted] = (0,
|
|
2172
|
-
const [attemptedButtonId, setAttemptedButtonId] = (0,
|
|
2378
|
+
function PreviewPane({ title, sections, onFieldChange, language, strings, chrome, baseMaxWidth, features, onSubmit }) {
|
|
2379
|
+
const [device, setDevice] = (0, import_react7.useState)("laptop");
|
|
2380
|
+
const [errors, setErrors] = (0, import_react7.useState)({});
|
|
2381
|
+
const [submitted, setSubmitted] = (0, import_react7.useState)(null);
|
|
2382
|
+
const [attemptedButtonId, setAttemptedButtonId] = (0, import_react7.useState)(null);
|
|
2173
2383
|
const deviceOptions = buildDeviceOptions(baseMaxWidth, chrome);
|
|
2174
2384
|
const maxWidth = (deviceOptions.find((d) => d.value === device) || deviceOptions[0]).maxWidth;
|
|
2175
2385
|
const allFields = sections.flatMap((s) => s.fields);
|
|
@@ -2228,7 +2438,7 @@ function PreviewPane({ title, sections, onFieldChange, language, strings, chrome
|
|
|
2228
2438
|
}
|
|
2229
2439
|
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { style: styles.previewWrap, children: [
|
|
2230
2440
|
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { style: { width: "100%", maxWidth, display: "flex", flexDirection: "column", alignItems: "center" }, children: [
|
|
2231
|
-
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { style: styles.previewToolbar, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Segmented, { options: deviceOptions, value: device, onChange: setDevice }) }),
|
|
2441
|
+
features.deviceToggle && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { style: styles.previewToolbar, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Segmented, { options: deviceOptions, value: device, onChange: setDevice }) }),
|
|
2232
2442
|
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { style: { ...styles.previewCard, maxWidth, width: "100%" }, children: [
|
|
2233
2443
|
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("h2", { style: styles.previewTitle, children: t(title, language) }),
|
|
2234
2444
|
allFields.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { style: { color: "var(--fb-muted)", fontSize: 14 }, children: strings.addFieldsHint }),
|
|
@@ -2274,17 +2484,66 @@ function PreviewPane({ title, sections, onFieldChange, language, strings, chrome
|
|
|
2274
2484
|
|
|
2275
2485
|
// src/components/modals/JsonModal.tsx
|
|
2276
2486
|
var import_lucide_react15 = require("lucide-react");
|
|
2487
|
+
|
|
2488
|
+
// src/hooks/useModalA11y.ts
|
|
2489
|
+
var import_react8 = require("react");
|
|
2490
|
+
var FOCUSABLE = 'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])';
|
|
2491
|
+
function useModalA11y(onClose) {
|
|
2492
|
+
const panelRef = (0, import_react8.useRef)(null);
|
|
2493
|
+
const onCloseRef = (0, import_react8.useRef)(onClose);
|
|
2494
|
+
onCloseRef.current = onClose;
|
|
2495
|
+
(0, import_react8.useEffect)(() => {
|
|
2496
|
+
const opener = document.activeElement;
|
|
2497
|
+
const panel = panelRef.current;
|
|
2498
|
+
const focusables = () => Array.from(panel?.querySelectorAll(FOCUSABLE) ?? []);
|
|
2499
|
+
if (panel && !panel.contains(document.activeElement)) {
|
|
2500
|
+
(panel.querySelector("[autofocus]") ?? focusables()[0] ?? panel).focus();
|
|
2501
|
+
}
|
|
2502
|
+
const onKeyDown = (e) => {
|
|
2503
|
+
if (e.key === "Escape") {
|
|
2504
|
+
e.stopPropagation();
|
|
2505
|
+
onCloseRef.current();
|
|
2506
|
+
return;
|
|
2507
|
+
}
|
|
2508
|
+
if (e.key !== "Tab") return;
|
|
2509
|
+
const items = focusables();
|
|
2510
|
+
if (items.length === 0) {
|
|
2511
|
+
e.preventDefault();
|
|
2512
|
+
return;
|
|
2513
|
+
}
|
|
2514
|
+
const first = items[0];
|
|
2515
|
+
const last = items[items.length - 1];
|
|
2516
|
+
const active = document.activeElement;
|
|
2517
|
+
if (e.shiftKey && (active === first || !panel?.contains(active))) {
|
|
2518
|
+
e.preventDefault();
|
|
2519
|
+
last.focus();
|
|
2520
|
+
} else if (!e.shiftKey && active === last) {
|
|
2521
|
+
e.preventDefault();
|
|
2522
|
+
first.focus();
|
|
2523
|
+
}
|
|
2524
|
+
};
|
|
2525
|
+
document.addEventListener("keydown", onKeyDown, true);
|
|
2526
|
+
return () => {
|
|
2527
|
+
document.removeEventListener("keydown", onKeyDown, true);
|
|
2528
|
+
opener?.focus?.();
|
|
2529
|
+
};
|
|
2530
|
+
}, []);
|
|
2531
|
+
return panelRef;
|
|
2532
|
+
}
|
|
2533
|
+
|
|
2534
|
+
// src/components/modals/JsonModal.tsx
|
|
2277
2535
|
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
2278
2536
|
function JsonModal({ chrome, jsonString, copied, onCopy, onClose }) {
|
|
2279
|
-
|
|
2537
|
+
const panelRef = useModalA11y(onClose);
|
|
2538
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: styles.modalOverlay, onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { ref: panelRef, style: styles.modal, role: "dialog", "aria-modal": "true", "aria-label": chrome.formJson, onClick: (e) => e.stopPropagation(), children: [
|
|
2280
2539
|
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { style: styles.modalHeader, children: [
|
|
2281
2540
|
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { style: { fontWeight: 600, fontSize: 14 }, children: chrome.formJson }),
|
|
2282
2541
|
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { style: { display: "flex", gap: 8 }, children: [
|
|
2283
|
-
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("button", { style: styles.ghostBtn, onClick: onCopy, children: [
|
|
2542
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("button", { type: "button", style: styles.ghostBtn, onClick: onCopy, children: [
|
|
2284
2543
|
copied ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react15.Check, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react15.ClipboardCopy, { size: 14 }),
|
|
2285
2544
|
copied ? chrome.copied : chrome.copy
|
|
2286
2545
|
] }),
|
|
2287
|
-
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { style: styles.iconBtn, onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react15.X, { size: 16 }) })
|
|
2546
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { type: "button", style: styles.iconBtn, onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react15.X, { size: 16 }) })
|
|
2288
2547
|
] })
|
|
2289
2548
|
] }),
|
|
2290
2549
|
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("pre", { style: styles.jsonPre, children: jsonString })
|
|
@@ -2294,55 +2553,97 @@ function JsonModal({ chrome, jsonString, copied, onCopy, onClose }) {
|
|
|
2294
2553
|
// src/components/modals/TemplatesModal.tsx
|
|
2295
2554
|
var import_lucide_react16 = require("lucide-react");
|
|
2296
2555
|
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
2297
|
-
function TemplatesModal({ chrome, savedForms, currentFormId, onOpen, onDelete, onClose }) {
|
|
2298
|
-
|
|
2556
|
+
function TemplatesModal({ chrome, savedForms, currentFormId, manage, templateState, onOpen, onDelete, onClose }) {
|
|
2557
|
+
const busy = templateState === "loading";
|
|
2558
|
+
const panelRef = useModalA11y(onClose);
|
|
2559
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: styles.modalOverlay, onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { ref: panelRef, style: styles.modal, role: "dialog", "aria-modal": "true", "aria-label": chrome.templates, onClick: (e) => e.stopPropagation(), children: [
|
|
2299
2560
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: styles.modalHeader, children: [
|
|
2300
2561
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: { fontWeight: 600, fontSize: 14 }, children: chrome.templates }),
|
|
2301
|
-
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { style: styles.iconBtn, onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react16.X, { size: 16 }) })
|
|
2562
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", style: styles.iconBtn, onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react16.X, { size: 16 }) })
|
|
2302
2563
|
] }),
|
|
2303
|
-
/* @__PURE__ */ (0, import_jsx_runtime16.
|
|
2304
|
-
/* @__PURE__ */ (0, import_jsx_runtime16.
|
|
2305
|
-
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style:
|
|
2306
|
-
|
|
2307
|
-
|
|
2564
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: styles.libraryBody, children: [
|
|
2565
|
+
savedForms.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: styles.inspectorEmpty, children: manage ? chrome.noTemplates : chrome.noTemplatesReadonly }) : savedForms.map((f) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: styles.libraryRow, children: [
|
|
2566
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { minWidth: 0 }, children: [
|
|
2567
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: styles.libraryRowTitle, children: [
|
|
2568
|
+
f.title,
|
|
2569
|
+
f.id === currentFormId && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: styles.currentBadge, children: chrome.current })
|
|
2570
|
+
] }),
|
|
2571
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: styles.libraryRowMeta, children: [
|
|
2572
|
+
chrome.updated,
|
|
2573
|
+
" ",
|
|
2574
|
+
new Date(f.updatedAt).toLocaleString()
|
|
2575
|
+
] })
|
|
2308
2576
|
] }),
|
|
2309
|
-
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style:
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2577
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { display: "flex", gap: 6, flexShrink: 0 }, children: [
|
|
2578
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("button", { type: "button", style: styles.ghostBtn, disabled: busy, onClick: () => onOpen(f.id), children: [
|
|
2579
|
+
busy && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react16.Loader2, { size: 12, className: "spin" }),
|
|
2580
|
+
" ",
|
|
2581
|
+
manage ? chrome.open : chrome.applyTemplate
|
|
2582
|
+
] }),
|
|
2583
|
+
manage && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", style: { ...styles.iconBtn, ...styles.iconBtnDanger }, disabled: busy, title: chrome.delete, onClick: () => onDelete(f.id), children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react16.Trash2, { size: 13 }) })
|
|
2313
2584
|
] })
|
|
2314
|
-
] }),
|
|
2315
|
-
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { display: "flex", gap:
|
|
2316
|
-
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
2317
|
-
|
|
2585
|
+
] }, f.id)),
|
|
2586
|
+
templateState === "error" && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { ...styles.libraryRowMeta, color: "var(--fb-danger)", display: "flex", alignItems: "center", gap: 5, marginTop: 4 }, children: [
|
|
2587
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react16.AlertCircle, { size: 12 }),
|
|
2588
|
+
" ",
|
|
2589
|
+
chrome.templateLoadFailed
|
|
2318
2590
|
] })
|
|
2319
|
-
] }
|
|
2591
|
+
] })
|
|
2320
2592
|
] }) });
|
|
2321
2593
|
}
|
|
2322
2594
|
|
|
2323
2595
|
// src/components/modals/SaveAsModal.tsx
|
|
2324
|
-
var
|
|
2596
|
+
var import_react9 = require("react");
|
|
2325
2597
|
var import_lucide_react17 = require("lucide-react");
|
|
2326
2598
|
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
2327
|
-
function SaveAsModal({ chrome, suggestedName, onSave, onClose }) {
|
|
2328
|
-
const [name, setName] = (0,
|
|
2329
|
-
|
|
2599
|
+
function SaveAsModal({ chrome, suggestedName, templateState, onSave, onClose }) {
|
|
2600
|
+
const [name, setName] = (0, import_react9.useState)(suggestedName);
|
|
2601
|
+
const saving = templateState === "saving";
|
|
2602
|
+
const canSave = !!name.trim() && !saving;
|
|
2603
|
+
const panelRef = useModalA11y(onClose);
|
|
2604
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { style: styles.modalOverlay, onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { ref: panelRef, style: { ...styles.modal, maxWidth: 380 }, role: "dialog", "aria-modal": "true", "aria-label": chrome.saveForm, onClick: (e) => e.stopPropagation(), children: [
|
|
2330
2605
|
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { style: styles.modalHeader, children: [
|
|
2331
2606
|
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { style: { fontWeight: 600, fontSize: 14 }, children: chrome.saveForm }),
|
|
2332
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { style: styles.iconBtn, onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react17.X, { size: 16 }) })
|
|
2607
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", style: styles.iconBtn, onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react17.X, { size: 16 }) })
|
|
2333
2608
|
] }),
|
|
2334
2609
|
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { style: { padding: 16 }, children: [
|
|
2335
2610
|
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("label", { style: styles.propLabel, children: chrome.formName }),
|
|
2336
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("input", { autoFocus: true, style: styles.propInput, value: name, onChange: (e) => setName(e.target.value), onKeyDown: (e) => e.key === "Enter" &&
|
|
2337
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("button", { style: { ...styles.primaryBtn, width: "100%", justifyContent: "center", marginTop: 14 }, disabled: !
|
|
2338
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react17.Save, { size: 14 }),
|
|
2611
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("input", { autoFocus: true, "aria-label": chrome.formName, style: styles.propInput, value: name, onChange: (e) => setName(e.target.value), onKeyDown: (e) => e.key === "Enter" && canSave && onSave(name.trim()) }),
|
|
2612
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("button", { type: "button", style: { ...styles.primaryBtn, width: "100%", justifyContent: "center", marginTop: 14 }, disabled: !canSave, onClick: () => onSave(name.trim()), children: [
|
|
2613
|
+
saving ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react17.Loader2, { size: 14, className: "spin" }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react17.Save, { size: 14 }),
|
|
2339
2614
|
" ",
|
|
2340
2615
|
chrome.saveToLibrary
|
|
2616
|
+
] }),
|
|
2617
|
+
templateState === "error" && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { style: { fontSize: 12, color: "var(--fb-danger)", display: "flex", alignItems: "center", gap: 5, marginTop: 10 }, children: [
|
|
2618
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react17.AlertCircle, { size: 12 }),
|
|
2619
|
+
" ",
|
|
2620
|
+
chrome.templateSaveFailed
|
|
2341
2621
|
] })
|
|
2342
2622
|
] })
|
|
2343
2623
|
] }) });
|
|
2344
2624
|
}
|
|
2345
2625
|
|
|
2626
|
+
// src/components/modals/ConfirmModal.tsx
|
|
2627
|
+
var import_lucide_react18 = require("lucide-react");
|
|
2628
|
+
var import_jsx_runtime18 = require("react/jsx-runtime");
|
|
2629
|
+
function ConfirmModal({ chrome, title, message, onConfirm, confirmLabel, tone = "default", onClose }) {
|
|
2630
|
+
const panelRef = useModalA11y(onClose);
|
|
2631
|
+
const confirmStyle = tone === "danger" ? { ...styles.primaryBtn, background: "var(--fb-danger)", borderColor: "var(--fb-danger)" } : styles.primaryBtn;
|
|
2632
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: styles.modalOverlay, onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { ref: panelRef, style: { ...styles.modal, maxWidth: 380 }, role: "alertdialog", "aria-modal": "true", "aria-label": title, onClick: (e) => e.stopPropagation(), children: [
|
|
2633
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: styles.modalHeader, children: [
|
|
2634
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { style: { fontWeight: 600, fontSize: 14 }, children: title }),
|
|
2635
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", style: styles.iconBtn, onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_lucide_react18.X, { size: 16 }) })
|
|
2636
|
+
] }),
|
|
2637
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { padding: 16 }, children: [
|
|
2638
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { style: { margin: 0, fontSize: 13, lineHeight: 1.5, color: "var(--fb-ink)" }, children: message }),
|
|
2639
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: { display: "flex", justifyContent: "flex-end", gap: 8, marginTop: 16 }, children: onConfirm ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
|
|
2640
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", autoFocus: true, style: styles.ghostBtn, onClick: onClose, children: chrome.cancel }),
|
|
2641
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", style: confirmStyle, onClick: onConfirm, children: confirmLabel ?? chrome.confirm })
|
|
2642
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", autoFocus: true, style: styles.primaryBtn, onClick: onClose, children: chrome.dismiss }) })
|
|
2643
|
+
] })
|
|
2644
|
+
] }) });
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2346
2647
|
// src/styles/globalCss.ts
|
|
2347
2648
|
var css = `
|
|
2348
2649
|
* { box-sizing: border-box; }
|
|
@@ -2350,20 +2651,14 @@ var css = `
|
|
|
2350
2651
|
input, textarea, select { font-family: inherit; }
|
|
2351
2652
|
@keyframes fb-spin { to { transform: rotate(360deg); } }
|
|
2352
2653
|
.spin { animation: fb-spin 0.8s linear infinite; }
|
|
2353
|
-
/*
|
|
2354
|
-
than growing past whatever space its host container gives it; the
|
|
2355
|
-
second declaration upgrades to the dynamic viewport unit on browsers
|
|
2356
|
-
that support it (mobile browser chrome resizing the viewport), and is
|
|
2357
|
-
simply ignored by ones that don't. */
|
|
2654
|
+
/* Cap at viewport height so the widget scrolls internally; dvh upgrade is ignored where unsupported. */
|
|
2358
2655
|
.fb-root { max-height: 100vh; max-height: 100dvh; }
|
|
2359
2656
|
|
|
2360
|
-
/*
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
buttons, tracked by .fb-work-area's data-mobile-panel attribute,
|
|
2366
|
-
and dismissible via .fb-mobile-backdrop. */
|
|
2657
|
+
/* Toolbar's active-template label is nice-to-have \u2014 drop it first when width is tight. */
|
|
2658
|
+
@media (max-width: 600px) { .fb-template-tag { display: none; } }
|
|
2659
|
+
|
|
2660
|
+
/* Under 720px: Palette/Inspector become drawers over Canvas, toggled by .fb-mobile-bar,
|
|
2661
|
+
tracked by .fb-work-area[data-mobile-panel], dismissed via .fb-mobile-backdrop. */
|
|
2367
2662
|
.fb-mobile-bar { display: none; align-items: center; gap: 6px; padding: 6px 10px; border-bottom: 1px solid var(--fb-border); background: var(--fb-surface); flex-shrink: 0; }
|
|
2368
2663
|
.fb-mobile-btn { display: flex; align-items: center; gap: 6px; padding: 6px 10px; border-radius: 7px; border: 1px solid var(--fb-border); background: var(--fb-surface); color: var(--fb-muted); font-size: 12.5px; font-weight: 600; }
|
|
2369
2664
|
.fb-mobile-btn[aria-pressed="true"] { border-color: var(--fb-primary); background: var(--fb-primary-soft); color: var(--fb-primary); }
|
|
@@ -2389,8 +2684,8 @@ var css = `
|
|
|
2389
2684
|
`;
|
|
2390
2685
|
|
|
2391
2686
|
// src/FormBuilder.tsx
|
|
2392
|
-
var
|
|
2393
|
-
var FormBuilder = (0,
|
|
2687
|
+
var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
2688
|
+
var FormBuilder = (0, import_react10.forwardRef)(function FormBuilder2({
|
|
2394
2689
|
theme: themeOverrideProp,
|
|
2395
2690
|
language: languageOverride,
|
|
2396
2691
|
languages = DEFAULT_LANGUAGES,
|
|
@@ -2399,24 +2694,40 @@ var FormBuilder = (0, import_react8.forwardRef)(function FormBuilder2({
|
|
|
2399
2694
|
features: featuresProp,
|
|
2400
2695
|
storage: storageProp,
|
|
2401
2696
|
onSubmit,
|
|
2402
|
-
initialDocument
|
|
2697
|
+
initialDocument,
|
|
2698
|
+
initialMode,
|
|
2699
|
+
onModeChange,
|
|
2700
|
+
onTemplateChange,
|
|
2701
|
+
templateClipboardKey
|
|
2403
2702
|
}, ref) {
|
|
2404
2703
|
const features = resolveFeatures(featuresProp);
|
|
2405
2704
|
const storage = storageProp ?? localStorageAdapter;
|
|
2406
2705
|
const { theme, updateThemeColor, updateThemeLayout, resetTheme, replaceThemeOverrides, themeOverrides } = useTheme(themeOverrideProp);
|
|
2407
|
-
const [language, setLanguage] = (0,
|
|
2706
|
+
const [language, setLanguage] = (0, import_react10.useState)(languageOverride || languages[0]?.code || "en");
|
|
2408
2707
|
const strings = { ...DEFAULT_STRINGS[language] || DEFAULT_STRINGS.en, ...stringsOverride && stringsOverride[language] || {} };
|
|
2409
2708
|
const chrome = { ...CHROME[language] || CHROME.en, ...chromeOverride && chromeOverride[language] || {} };
|
|
2410
2709
|
const doc = useFormDocument({ language, chrome });
|
|
2411
2710
|
const drag = useDragReorder(doc.reorderWithinSection);
|
|
2412
|
-
const [mode,
|
|
2413
|
-
const
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2711
|
+
const [mode, setModeState] = (0, import_react10.useState)(initialMode ?? "build");
|
|
2712
|
+
const setMode = (next) => {
|
|
2713
|
+
setModeState(next);
|
|
2714
|
+
onModeChange?.(next);
|
|
2715
|
+
};
|
|
2716
|
+
(0, import_react10.useEffect)(() => {
|
|
2717
|
+
onModeChange?.(mode);
|
|
2718
|
+
}, []);
|
|
2719
|
+
const [showJson, setShowJson] = (0, import_react10.useState)(false);
|
|
2720
|
+
const [showLibrary, setShowLibrary] = (0, import_react10.useState)(false);
|
|
2721
|
+
const [copied, setCopied] = (0, import_react10.useState)(false);
|
|
2722
|
+
const [templateCopied, setTemplateCopied] = (0, import_react10.useState)(false);
|
|
2723
|
+
const [pastePrompt, setPastePrompt] = (0, import_react10.useState)(false);
|
|
2724
|
+
const clipboard = useTemplateClipboard(templateClipboardKey);
|
|
2725
|
+
const [mobilePanel, setMobilePanel] = (0, import_react10.useState)("none");
|
|
2417
2726
|
const persistence = usePersistence({
|
|
2418
2727
|
storage,
|
|
2419
2728
|
autosave: features.autosave,
|
|
2729
|
+
templateMax: features.templates.max,
|
|
2730
|
+
templateManage: features.templates.manage,
|
|
2420
2731
|
language,
|
|
2421
2732
|
chrome,
|
|
2422
2733
|
document: { title: doc.title, themeOverrides, sections: doc.sections },
|
|
@@ -2428,16 +2739,17 @@ var FormBuilder = (0, import_react8.forwardRef)(function FormBuilder2({
|
|
|
2428
2739
|
doc.resetToBlank();
|
|
2429
2740
|
resetTheme();
|
|
2430
2741
|
},
|
|
2742
|
+
onTemplateChange,
|
|
2431
2743
|
ensureActiveSection: () => doc.setActiveSectionId((prev) => prev ?? doc.sections[0]?.id ?? null)
|
|
2432
2744
|
});
|
|
2433
|
-
const jsonDoc = {
|
|
2745
|
+
const jsonDoc = (0, import_react10.useMemo)(() => ({
|
|
2434
2746
|
version: 5,
|
|
2435
2747
|
title: doc.title,
|
|
2436
2748
|
theme,
|
|
2437
2749
|
themeOverrides,
|
|
2438
2750
|
sections: doc.sections.map((s) => ({ id: s.id, title: s.title, background: s.background, collapsed: s.collapsed, fields: s.fields }))
|
|
2439
|
-
};
|
|
2440
|
-
const jsonString = JSON.stringify(jsonDoc, null, 2);
|
|
2751
|
+
}), [doc.title, doc.sections, theme, themeOverrides]);
|
|
2752
|
+
const jsonString = (0, import_react10.useMemo)(() => JSON.stringify(jsonDoc, null, 2), [jsonDoc]);
|
|
2441
2753
|
function copyJson() {
|
|
2442
2754
|
navigator.clipboard.writeText(jsonString).then(() => {
|
|
2443
2755
|
setCopied(true);
|
|
@@ -2445,17 +2757,31 @@ var FormBuilder = (0, import_react8.forwardRef)(function FormBuilder2({
|
|
|
2445
2757
|
}).catch(() => {
|
|
2446
2758
|
});
|
|
2447
2759
|
}
|
|
2448
|
-
|
|
2760
|
+
function applyDocument(migrated) {
|
|
2761
|
+
if (!migrated) return false;
|
|
2762
|
+
doc.loadDocument(migrated);
|
|
2763
|
+
replaceThemeOverrides(migrated.themeOverrides);
|
|
2764
|
+
return true;
|
|
2765
|
+
}
|
|
2766
|
+
function copyTemplate() {
|
|
2767
|
+
clipboard.copyTemplate(jsonDoc);
|
|
2768
|
+
setTemplateCopied(true);
|
|
2769
|
+
setTimeout(() => setTemplateCopied(false), 1500);
|
|
2770
|
+
}
|
|
2771
|
+
function pasteTemplate() {
|
|
2772
|
+
if (clipboard.readTemplate()) setPastePrompt(true);
|
|
2773
|
+
}
|
|
2774
|
+
(0, import_react10.useImperativeHandle)(ref, () => ({
|
|
2449
2775
|
getDocument: () => jsonDoc,
|
|
2450
2776
|
exportJson: () => jsonString,
|
|
2451
2777
|
loadDocument: (raw) => {
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
}
|
|
2778
|
+
applyDocument(migrateDocument(raw));
|
|
2779
|
+
},
|
|
2780
|
+
getTemplate: () => ({ __fpb: "template", v: TEMPLATE_FORMAT, document: jsonDoc }),
|
|
2781
|
+
loadTemplate: (input) => applyDocument(parseTemplate(typeof input === "string" ? input : JSON.stringify(input)))
|
|
2457
2782
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2458
2783
|
}), [jsonDoc, jsonString]);
|
|
2784
|
+
const fieldCount = doc.sections.reduce((n, s) => n + s.fields.filter((f) => !getMeta(f.type).isContent).length, 0);
|
|
2459
2785
|
const activeSectionIdx = doc.sections.findIndex((s) => s.id === doc.activeSection?.id);
|
|
2460
2786
|
const activeSectionLabel = t(doc.activeSection?.title, language) || `#${activeSectionIdx + 1}`;
|
|
2461
2787
|
const rootStyle = {
|
|
@@ -2479,9 +2805,9 @@ var FormBuilder = (0, import_react8.forwardRef)(function FormBuilder2({
|
|
|
2479
2805
|
"--fb-space-panel": `${theme.layout.panelPadding}px`,
|
|
2480
2806
|
"--fb-space-ticket": `${theme.layout.ticketPadding}px`
|
|
2481
2807
|
};
|
|
2482
|
-
return /* @__PURE__ */ (0,
|
|
2483
|
-
/* @__PURE__ */ (0,
|
|
2484
|
-
/* @__PURE__ */ (0,
|
|
2808
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "fb-root", style: rootStyle, children: [
|
|
2809
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("style", { children: css }),
|
|
2810
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
2485
2811
|
Toolbar,
|
|
2486
2812
|
{
|
|
2487
2813
|
title: doc.title,
|
|
@@ -2489,24 +2815,31 @@ var FormBuilder = (0, import_react8.forwardRef)(function FormBuilder2({
|
|
|
2489
2815
|
languages,
|
|
2490
2816
|
mode,
|
|
2491
2817
|
saveState: persistence.saveState,
|
|
2818
|
+
templateState: persistence.templateState,
|
|
2819
|
+
activeTemplateTitle: persistence.activeTemplateTitle,
|
|
2820
|
+
templateDirty: persistence.isTemplateDirty,
|
|
2492
2821
|
chrome,
|
|
2493
2822
|
features,
|
|
2494
2823
|
savedFormsCount: persistence.savedForms.length,
|
|
2824
|
+
clipboardReady: clipboard.hasClipboard,
|
|
2825
|
+
templateCopied,
|
|
2495
2826
|
onTitleChange: doc.updateTitle,
|
|
2496
2827
|
onLanguageChange: setLanguage,
|
|
2497
2828
|
onModeChange: setMode,
|
|
2498
2829
|
onNewForm: persistence.newForm,
|
|
2499
2830
|
onOpenLibrary: () => setShowLibrary(true),
|
|
2500
2831
|
onSaveExisting: persistence.saveExisting,
|
|
2501
|
-
onOpenJson: () => setShowJson(true)
|
|
2832
|
+
onOpenJson: () => setShowJson(true),
|
|
2833
|
+
onCopyTemplate: copyTemplate,
|
|
2834
|
+
onPasteTemplate: pasteTemplate
|
|
2502
2835
|
}
|
|
2503
2836
|
),
|
|
2504
|
-
persistence.loadingDraft ? /* @__PURE__ */ (0,
|
|
2505
|
-
/* @__PURE__ */ (0,
|
|
2506
|
-
/* @__PURE__ */ (0,
|
|
2507
|
-
] }) : mode === "build" ? /* @__PURE__ */ (0,
|
|
2508
|
-
/* @__PURE__ */ (0,
|
|
2509
|
-
/* @__PURE__ */ (0,
|
|
2837
|
+
persistence.loadingDraft ? /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: styles.loadingScreen, children: [
|
|
2838
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react19.Loader2, { size: 20, className: "spin" }),
|
|
2839
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { style: { marginTop: 8, fontSize: 13, color: "var(--fb-muted)" }, children: chrome.loadingDraft })
|
|
2840
|
+
] }) : mode === "build" ? /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "fb-work-area", style: styles.workArea, "data-mobile-panel": mobilePanel, children: [
|
|
2841
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "fb-mobile-bar", children: [
|
|
2842
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
|
|
2510
2843
|
"button",
|
|
2511
2844
|
{
|
|
2512
2845
|
type: "button",
|
|
@@ -2514,13 +2847,13 @@ var FormBuilder = (0, import_react8.forwardRef)(function FormBuilder2({
|
|
|
2514
2847
|
"aria-pressed": mobilePanel === "palette",
|
|
2515
2848
|
onClick: () => setMobilePanel((p) => p === "palette" ? "none" : "palette"),
|
|
2516
2849
|
children: [
|
|
2517
|
-
/* @__PURE__ */ (0,
|
|
2850
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react19.Menu, { size: 14 }),
|
|
2518
2851
|
" ",
|
|
2519
2852
|
chrome.paletteTabBlocks
|
|
2520
2853
|
]
|
|
2521
2854
|
}
|
|
2522
2855
|
),
|
|
2523
|
-
/* @__PURE__ */ (0,
|
|
2856
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
|
|
2524
2857
|
"button",
|
|
2525
2858
|
{
|
|
2526
2859
|
type: "button",
|
|
@@ -2528,16 +2861,16 @@ var FormBuilder = (0, import_react8.forwardRef)(function FormBuilder2({
|
|
|
2528
2861
|
"aria-pressed": mobilePanel === "inspector",
|
|
2529
2862
|
onClick: () => setMobilePanel((p) => p === "inspector" ? "none" : "inspector"),
|
|
2530
2863
|
children: [
|
|
2531
|
-
/* @__PURE__ */ (0,
|
|
2864
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react19.SlidersHorizontal, { size: 14 }),
|
|
2532
2865
|
" ",
|
|
2533
2866
|
chrome.properties
|
|
2534
2867
|
]
|
|
2535
2868
|
}
|
|
2536
2869
|
)
|
|
2537
2870
|
] }),
|
|
2538
|
-
/* @__PURE__ */ (0,
|
|
2539
|
-
mobilePanel !== "none" && /* @__PURE__ */ (0,
|
|
2540
|
-
/* @__PURE__ */ (0,
|
|
2871
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "fb-canvas-area", style: styles.workArea, children: [
|
|
2872
|
+
mobilePanel !== "none" && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "fb-mobile-backdrop", onClick: () => setMobilePanel("none") }),
|
|
2873
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
2541
2874
|
Palette,
|
|
2542
2875
|
{
|
|
2543
2876
|
activeSectionLabel,
|
|
@@ -2547,13 +2880,14 @@ var FormBuilder = (0, import_react8.forwardRef)(function FormBuilder2({
|
|
|
2547
2880
|
setMobilePanel("none");
|
|
2548
2881
|
},
|
|
2549
2882
|
features,
|
|
2883
|
+
fieldCount,
|
|
2550
2884
|
theme,
|
|
2551
2885
|
updateThemeColor,
|
|
2552
2886
|
updateThemeLayout,
|
|
2553
2887
|
resetTheme
|
|
2554
2888
|
}
|
|
2555
2889
|
),
|
|
2556
|
-
/* @__PURE__ */ (0,
|
|
2890
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
2557
2891
|
Canvas,
|
|
2558
2892
|
{
|
|
2559
2893
|
sections: doc.sections,
|
|
@@ -2585,7 +2919,7 @@ var FormBuilder = (0, import_react8.forwardRef)(function FormBuilder2({
|
|
|
2585
2919
|
getDragHandleProps: drag.getDragHandleProps
|
|
2586
2920
|
}
|
|
2587
2921
|
),
|
|
2588
|
-
/* @__PURE__ */ (0,
|
|
2922
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
2589
2923
|
Inspector,
|
|
2590
2924
|
{
|
|
2591
2925
|
selected: doc.selected,
|
|
@@ -2600,7 +2934,7 @@ var FormBuilder = (0, import_react8.forwardRef)(function FormBuilder2({
|
|
|
2600
2934
|
}
|
|
2601
2935
|
)
|
|
2602
2936
|
] })
|
|
2603
|
-
] }) : /* @__PURE__ */ (0,
|
|
2937
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
2604
2938
|
PreviewPane,
|
|
2605
2939
|
{
|
|
2606
2940
|
title: doc.title,
|
|
@@ -2610,32 +2944,59 @@ var FormBuilder = (0, import_react8.forwardRef)(function FormBuilder2({
|
|
|
2610
2944
|
strings,
|
|
2611
2945
|
chrome,
|
|
2612
2946
|
baseMaxWidth: theme.layout.maxWidth,
|
|
2947
|
+
features,
|
|
2613
2948
|
onSubmit
|
|
2614
2949
|
}
|
|
2615
2950
|
),
|
|
2616
|
-
showJson && /* @__PURE__ */ (0,
|
|
2617
|
-
showLibrary && /* @__PURE__ */ (0,
|
|
2951
|
+
showJson && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(JsonModal, { chrome, jsonString, copied, onCopy: copyJson, onClose: () => setShowJson(false) }),
|
|
2952
|
+
showLibrary && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
2618
2953
|
TemplatesModal,
|
|
2619
2954
|
{
|
|
2620
2955
|
chrome,
|
|
2621
2956
|
savedForms: persistence.savedForms,
|
|
2622
2957
|
currentFormId: persistence.currentFormId,
|
|
2958
|
+
manage: features.templates.manage,
|
|
2959
|
+
templateState: persistence.templateState,
|
|
2623
2960
|
onOpen: async (id) => {
|
|
2624
|
-
await persistence.loadForm(id);
|
|
2625
|
-
setShowLibrary(false);
|
|
2961
|
+
if (await persistence.loadForm(id)) setShowLibrary(false);
|
|
2626
2962
|
},
|
|
2627
2963
|
onDelete: persistence.deleteForm,
|
|
2628
2964
|
onClose: () => setShowLibrary(false)
|
|
2629
2965
|
}
|
|
2630
2966
|
),
|
|
2631
|
-
persistence.saveAsPrompt && /* @__PURE__ */ (0,
|
|
2967
|
+
persistence.saveAsPrompt && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
2632
2968
|
SaveAsModal,
|
|
2633
2969
|
{
|
|
2634
2970
|
chrome,
|
|
2635
2971
|
suggestedName: persistence.saveAsPrompt.suggestedName,
|
|
2972
|
+
templateState: persistence.templateState,
|
|
2636
2973
|
onSave: persistence.saveAs,
|
|
2637
2974
|
onClose: persistence.dismissSaveAsPrompt
|
|
2638
2975
|
}
|
|
2976
|
+
),
|
|
2977
|
+
pastePrompt && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
2978
|
+
ConfirmModal,
|
|
2979
|
+
{
|
|
2980
|
+
chrome,
|
|
2981
|
+
title: chrome.pasteTemplate,
|
|
2982
|
+
message: chrome.pasteTemplateConfirm,
|
|
2983
|
+
confirmLabel: chrome.confirmReplace,
|
|
2984
|
+
tone: "danger",
|
|
2985
|
+
onConfirm: () => {
|
|
2986
|
+
applyDocument(clipboard.readTemplate());
|
|
2987
|
+
setPastePrompt(false);
|
|
2988
|
+
},
|
|
2989
|
+
onClose: () => setPastePrompt(false)
|
|
2990
|
+
}
|
|
2991
|
+
),
|
|
2992
|
+
persistence.notice && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
2993
|
+
ConfirmModal,
|
|
2994
|
+
{
|
|
2995
|
+
chrome,
|
|
2996
|
+
title: persistence.notice.title,
|
|
2997
|
+
message: persistence.notice.message,
|
|
2998
|
+
onClose: persistence.dismissNotice
|
|
2999
|
+
}
|
|
2639
3000
|
)
|
|
2640
3001
|
] });
|
|
2641
3002
|
});
|
|
@@ -2657,9 +3018,16 @@ var DARK_THEME = {
|
|
|
2657
3018
|
};
|
|
2658
3019
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2659
3020
|
0 && (module.exports = {
|
|
3021
|
+
CLIPBOARD_KEY,
|
|
2660
3022
|
DARK_THEME,
|
|
2661
3023
|
DEFAULT_THEME,
|
|
3024
|
+
DRAFT_KEY,
|
|
2662
3025
|
FormBuilder,
|
|
2663
|
-
|
|
3026
|
+
INDEX_KEY,
|
|
3027
|
+
formKey,
|
|
3028
|
+
localStorageAdapter,
|
|
3029
|
+
parseTemplate,
|
|
3030
|
+
savedFormId,
|
|
3031
|
+
serializeTemplate
|
|
2664
3032
|
});
|
|
2665
3033
|
//# sourceMappingURL=index.cjs.map
|