form-page-builder 2.1.0 → 2.3.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 +63 -15
- package/dist/index.cjs +502 -187
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +91 -16
- package/dist/index.d.ts +91 -16
- package/dist/index.js +497 -190
- package/dist/index.js.map +1 -1
- package/package.json +12 -2
package/dist/index.cjs
CHANGED
|
@@ -21,17 +21,25 @@ 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
|
+
DOCUMENT_VERSION: () => DOCUMENT_VERSION,
|
|
28
|
+
DRAFT_KEY: () => DRAFT_KEY,
|
|
26
29
|
FormBuilder: () => FormBuilder_default,
|
|
30
|
+
INDEX_KEY: () => INDEX_KEY,
|
|
27
31
|
default: () => FormBuilder_default,
|
|
28
|
-
|
|
32
|
+
formKey: () => formKey,
|
|
33
|
+
localStorageAdapter: () => localStorageAdapter,
|
|
34
|
+
parseTemplate: () => parseTemplate,
|
|
35
|
+
savedFormId: () => savedFormId,
|
|
36
|
+
serializeTemplate: () => serializeTemplate
|
|
29
37
|
});
|
|
30
38
|
module.exports = __toCommonJS(index_exports);
|
|
31
39
|
|
|
32
40
|
// src/FormBuilder.tsx
|
|
33
|
-
var
|
|
34
|
-
var
|
|
41
|
+
var import_react10 = require("react");
|
|
42
|
+
var import_lucide_react19 = require("lucide-react");
|
|
35
43
|
|
|
36
44
|
// src/constants/fieldTypes.ts
|
|
37
45
|
var import_lucide_react = require("lucide-react");
|
|
@@ -122,6 +130,7 @@ var DEFAULT_STRINGS = {
|
|
|
122
130
|
tooLarge: (n) => `${n}\u4EE5\u4E0B\u306E\u5024\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
|
|
123
131
|
}
|
|
124
132
|
};
|
|
133
|
+
DEFAULT_STRINGS.ja;
|
|
125
134
|
|
|
126
135
|
// src/i18n/chrome.ts
|
|
127
136
|
var CHROME = {
|
|
@@ -197,16 +206,33 @@ var CHROME = {
|
|
|
197
206
|
formJson: "Form JSON",
|
|
198
207
|
copy: "Copy",
|
|
199
208
|
copied: "Copied",
|
|
209
|
+
copyTemplate: "Copy template",
|
|
210
|
+
pasteTemplate: "Paste template",
|
|
211
|
+
copyTemplateTitle: "Copy this form as a template you can paste into another builder",
|
|
212
|
+
pasteTemplateTitle: "Replace this form with the copied template",
|
|
213
|
+
pasteTemplateConfirm: "Replace the current form with the copied template? This can't be undone.",
|
|
200
214
|
noTemplates: 'No templates saved yet. Click "Save" in the toolbar to store this form here.',
|
|
215
|
+
noTemplatesReadonly: "No templates available yet.",
|
|
201
216
|
updated: "Updated",
|
|
202
217
|
open: "Open",
|
|
218
|
+
applyTemplate: "Apply",
|
|
203
219
|
saveForm: "Save as template",
|
|
204
220
|
formName: "Template name",
|
|
205
221
|
saveToLibrary: "Save template",
|
|
206
222
|
loadingDraft: "Loading your draft...",
|
|
207
|
-
templatesLimitReached:
|
|
223
|
+
templatesLimitReached: (max) => `You can save up to ${max} template${max === 1 ? "" : "s"}. Delete one to save another.`,
|
|
224
|
+
templatesLimitTitle: "Template limit reached",
|
|
225
|
+
confirm: "Confirm",
|
|
226
|
+
cancel: "Cancel",
|
|
227
|
+
dismiss: "OK",
|
|
228
|
+
confirmReplace: "Replace",
|
|
208
229
|
current: "current",
|
|
209
230
|
customColor: "Custom color",
|
|
231
|
+
templateLabel: (name) => `Template: ${name}`,
|
|
232
|
+
templateEdited: "Edited",
|
|
233
|
+
templateLoading: "Loading template...",
|
|
234
|
+
templateLoadFailed: "Couldn't load that template.",
|
|
235
|
+
templateSaveFailed: "Couldn't save the template.",
|
|
210
236
|
fieldsCount: (n) => `${n} field${n === 1 ? "" : "s"}`,
|
|
211
237
|
fieldTypeInput: "Input",
|
|
212
238
|
fieldTypeTextarea: "Textarea",
|
|
@@ -333,16 +359,33 @@ var CHROME = {
|
|
|
333
359
|
formJson: "\u30D5\u30A9\u30FC\u30E0JSON",
|
|
334
360
|
copy: "\u30B3\u30D4\u30FC",
|
|
335
361
|
copied: "\u30B3\u30D4\u30FC\u3057\u307E\u3057\u305F",
|
|
362
|
+
copyTemplate: "\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u3092\u30B3\u30D4\u30FC",
|
|
363
|
+
pasteTemplate: "\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u3092\u8CBC\u308A\u4ED8\u3051",
|
|
364
|
+
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",
|
|
365
|
+
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",
|
|
366
|
+
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",
|
|
336
367
|
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",
|
|
368
|
+
noTemplatesReadonly: "\u5229\u7528\u3067\u304D\u308B\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u304C\u307E\u3060\u3042\u308A\u307E\u305B\u3093\u3002",
|
|
337
369
|
updated: "\u66F4\u65B0\u65E5\u6642",
|
|
338
370
|
open: "\u958B\u304F",
|
|
371
|
+
applyTemplate: "\u9069\u7528",
|
|
339
372
|
saveForm: "\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u3068\u3057\u3066\u4FDD\u5B58",
|
|
340
373
|
formName: "\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u540D",
|
|
341
374
|
saveToLibrary: "\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u3092\u4FDD\u5B58",
|
|
342
375
|
loadingDraft: "\u4E0B\u66F8\u304D\u3092\u8AAD\u307F\u8FBC\u3093\u3067\u3044\u307E\u3059...",
|
|
343
|
-
templatesLimitReached:
|
|
376
|
+
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`,
|
|
377
|
+
templatesLimitTitle: "\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u306E\u4E0A\u9650\u306B\u9054\u3057\u307E\u3057\u305F",
|
|
378
|
+
confirm: "\u78BA\u5B9A",
|
|
379
|
+
cancel: "\u30AD\u30E3\u30F3\u30BB\u30EB",
|
|
380
|
+
dismiss: "OK",
|
|
381
|
+
confirmReplace: "\u7F6E\u304D\u63DB\u3048\u308B",
|
|
344
382
|
current: "\u73FE\u5728\u7DE8\u96C6\u4E2D",
|
|
345
383
|
customColor: "\u30AB\u30B9\u30BF\u30E0\u30AB\u30E9\u30FC",
|
|
384
|
+
templateLabel: (name) => `\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8: ${name}`,
|
|
385
|
+
templateEdited: "\u7DE8\u96C6\u6E08\u307F",
|
|
386
|
+
templateLoading: "\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u3092\u8AAD\u307F\u8FBC\u307F\u4E2D...",
|
|
387
|
+
templateLoadFailed: "\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u3092\u8AAD\u307F\u8FBC\u3081\u307E\u305B\u3093\u3067\u3057\u305F\u3002",
|
|
388
|
+
templateSaveFailed: "\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u3092\u4FDD\u5B58\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002",
|
|
346
389
|
fieldsCount: (n) => `${n}\u4EF6\u306E\u30D5\u30A3\u30FC\u30EB\u30C9`,
|
|
347
390
|
fieldTypeInput: "\u5165\u529B",
|
|
348
391
|
fieldTypeTextarea: "\u30C6\u30AD\u30B9\u30C8\u30A8\u30EA\u30A2",
|
|
@@ -398,6 +441,7 @@ var CHROME = {
|
|
|
398
441
|
spacingTicketPadding: "\u30D5\u30A3\u30FC\u30EB\u30C9\u5185\u4F59\u767D"
|
|
399
442
|
}
|
|
400
443
|
};
|
|
444
|
+
CHROME.ja;
|
|
401
445
|
|
|
402
446
|
// src/lib/bilingual.ts
|
|
403
447
|
function bi(en = "", ja = "") {
|
|
@@ -540,12 +584,8 @@ function defaultSection(titleEn = "") {
|
|
|
540
584
|
return { id: genSectionId(), title: bi(titleEn, ""), background: "", collapsed: false, fields: [] };
|
|
541
585
|
}
|
|
542
586
|
|
|
543
|
-
// src/lib/submittable.ts
|
|
544
|
-
function sectionHasFormFields(section) {
|
|
545
|
-
return section.fields.some((f) => !getMeta(f.type).isContent);
|
|
546
|
-
}
|
|
547
|
-
|
|
548
587
|
// src/lib/migrate.ts
|
|
588
|
+
var DOCUMENT_VERSION = 5;
|
|
549
589
|
function migrateField(field) {
|
|
550
590
|
let f = { ...field };
|
|
551
591
|
if (LEGACY_INPUT_TYPES.includes(f.type)) f = { ...f, type: "input", inputType: f.type };
|
|
@@ -576,25 +616,6 @@ function migrateField(field) {
|
|
|
576
616
|
function migrateFields(fields) {
|
|
577
617
|
return (fields || []).map(migrateField);
|
|
578
618
|
}
|
|
579
|
-
function synthesizeButtonField(label, style, scope) {
|
|
580
|
-
const resolvedLabel = typeof label === "string" && label ? bi(label) : label && typeof label === "object" && (label.en || label.ja) ? label : bi("Submit", "");
|
|
581
|
-
return {
|
|
582
|
-
id: nextId(),
|
|
583
|
-
type: "button",
|
|
584
|
-
label: resolvedLabel,
|
|
585
|
-
hideLabel: false,
|
|
586
|
-
width: "1/1",
|
|
587
|
-
verticalAlign: "top",
|
|
588
|
-
labelPosition: "top",
|
|
589
|
-
showIcon: false,
|
|
590
|
-
displayIcon: "Type",
|
|
591
|
-
action: "submit",
|
|
592
|
-
submitScope: scope,
|
|
593
|
-
buttonStyle: style || { color: "", size: "md" },
|
|
594
|
-
href: "",
|
|
595
|
-
target: "_self"
|
|
596
|
-
};
|
|
597
|
-
}
|
|
598
619
|
function migrateDocument(raw) {
|
|
599
620
|
if (!raw) return null;
|
|
600
621
|
const title = typeof raw.title === "string" ? bi(raw.title) : raw.title || bi();
|
|
@@ -611,32 +632,36 @@ function migrateDocument(raw) {
|
|
|
611
632
|
} else {
|
|
612
633
|
sections = [{ ...defaultSection(), fields: migrateFields(raw.fields || []) }];
|
|
613
634
|
}
|
|
614
|
-
const legacy = raw.version === void 0 || raw.version < 5;
|
|
615
|
-
if (legacy) {
|
|
616
|
-
resyncIdCounter(sections.flatMap((s) => s.fields));
|
|
617
|
-
const submitMode = raw.submitMode === "perSection" ? "perSection" : "combined";
|
|
618
|
-
const submitLabel = raw.submitLabel;
|
|
619
|
-
const submitStyle = raw.submitStyle || { color: "", size: "md" };
|
|
620
|
-
if (submitMode === "perSection" && raw.sections) {
|
|
621
|
-
sections = sections.map((s, i) => {
|
|
622
|
-
if (!sectionHasFormFields(s)) return s;
|
|
623
|
-
const rawSection = raw.sections[i] || {};
|
|
624
|
-
const label = rawSection.submitLabel || submitLabel;
|
|
625
|
-
const style = rawSection.submitStyle || submitStyle;
|
|
626
|
-
return { ...s, fields: [...s.fields, synthesizeButtonField(label, style, "section")] };
|
|
627
|
-
});
|
|
628
|
-
} else if (sections.some(sectionHasFormFields)) {
|
|
629
|
-
const lastIdx = sections.length - 1;
|
|
630
|
-
sections = sections.map((s, i) => i === lastIdx ? { ...s, fields: [...s.fields, synthesizeButtonField(submitLabel, submitStyle, "form")] } : s);
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
635
|
return { title, themeOverrides, sections };
|
|
634
636
|
}
|
|
635
637
|
|
|
638
|
+
// src/lib/template.ts
|
|
639
|
+
var TEMPLATE_FORMAT = 1;
|
|
640
|
+
function serializeTemplate(document2) {
|
|
641
|
+
const envelope = { __fpb: "template", v: TEMPLATE_FORMAT, document: document2 };
|
|
642
|
+
return JSON.stringify(envelope);
|
|
643
|
+
}
|
|
644
|
+
function parseTemplate(raw) {
|
|
645
|
+
if (!raw) return null;
|
|
646
|
+
let parsed;
|
|
647
|
+
try {
|
|
648
|
+
parsed = JSON.parse(raw);
|
|
649
|
+
} catch {
|
|
650
|
+
return null;
|
|
651
|
+
}
|
|
652
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
653
|
+
const obj = parsed;
|
|
654
|
+
const candidate = obj.__fpb === "template" && obj.document ? obj.document : obj;
|
|
655
|
+
return migrateDocument(candidate);
|
|
656
|
+
}
|
|
657
|
+
|
|
636
658
|
// src/lib/features.ts
|
|
659
|
+
var DEFAULT_MAX_TEMPLATES = 5;
|
|
637
660
|
var DEFAULT_FEATURES = {
|
|
638
661
|
naming: true,
|
|
639
|
-
|
|
662
|
+
formTitle: false,
|
|
663
|
+
templates: { enabled: true, manage: true, max: DEFAULT_MAX_TEMPLATES },
|
|
664
|
+
templateClipboard: true,
|
|
640
665
|
newForm: true,
|
|
641
666
|
autosave: true,
|
|
642
667
|
jsonView: true,
|
|
@@ -647,15 +672,24 @@ var DEFAULT_FEATURES = {
|
|
|
647
672
|
contentBlocks: true,
|
|
648
673
|
fieldTypes: true,
|
|
649
674
|
sections: true,
|
|
675
|
+
sectionBackground: true,
|
|
650
676
|
dragReorder: true,
|
|
651
677
|
deviceToggle: true
|
|
652
678
|
};
|
|
679
|
+
function resolveTemplates(t2) {
|
|
680
|
+
if (t2 === false) return { enabled: false, manage: false, max: DEFAULT_MAX_TEMPLATES };
|
|
681
|
+
if (t2 === void 0 || t2 === true) return { enabled: true, manage: true, max: DEFAULT_MAX_TEMPLATES };
|
|
682
|
+
return { enabled: true, manage: t2.manage ?? true, max: t2.max ?? DEFAULT_MAX_TEMPLATES };
|
|
683
|
+
}
|
|
653
684
|
function resolveFeatures(features) {
|
|
654
685
|
return {
|
|
655
686
|
...DEFAULT_FEATURES,
|
|
656
687
|
...features,
|
|
688
|
+
templates: resolveTemplates(features?.templates),
|
|
657
689
|
contentBlocks: Array.isArray(features?.contentBlocks) ? new Set(features.contentBlocks) : features?.contentBlocks ?? DEFAULT_FEATURES.contentBlocks,
|
|
658
|
-
fieldTypes: Array.isArray(features?.fieldTypes) ? new Set(features.fieldTypes) : features?.fieldTypes ?? DEFAULT_FEATURES.fieldTypes
|
|
690
|
+
fieldTypes: Array.isArray(features?.fieldTypes) ? new Set(features.fieldTypes) : features?.fieldTypes ?? DEFAULT_FEATURES.fieldTypes,
|
|
691
|
+
// Unset → follow `sections`, so an existing `sections: false` still hides the picker.
|
|
692
|
+
sectionBackground: features?.sectionBackground ?? features?.sections ?? DEFAULT_FEATURES.sectionBackground
|
|
659
693
|
};
|
|
660
694
|
}
|
|
661
695
|
function isContentBlockEnabled(features, type) {
|
|
@@ -685,17 +719,17 @@ var DEFAULT_THEME = {
|
|
|
685
719
|
|
|
686
720
|
// src/hooks/useTheme.ts
|
|
687
721
|
function useTheme(themeOverrideProp) {
|
|
688
|
-
const
|
|
722
|
+
const [themeOverrides, setThemeOverrides] = (0, import_react.useState)({});
|
|
723
|
+
const baseTheme = (0, import_react.useMemo)(() => ({
|
|
689
724
|
...DEFAULT_THEME,
|
|
690
725
|
...themeOverrideProp || {},
|
|
691
726
|
layout: { ...DEFAULT_THEME.layout, ...themeOverrideProp && themeOverrideProp.layout || {} }
|
|
692
|
-
};
|
|
693
|
-
const
|
|
694
|
-
const theme = {
|
|
727
|
+
}), [themeOverrideProp]);
|
|
728
|
+
const theme = (0, import_react.useMemo)(() => ({
|
|
695
729
|
...baseTheme,
|
|
696
730
|
...themeOverrides,
|
|
697
731
|
layout: { ...baseTheme.layout, ...themeOverrides.layout || {} }
|
|
698
|
-
};
|
|
732
|
+
}), [baseTheme, themeOverrides]);
|
|
699
733
|
function updateThemeColor(key, value) {
|
|
700
734
|
setThemeOverrides((prev) => ({ ...prev, [key]: value }));
|
|
701
735
|
}
|
|
@@ -894,35 +928,49 @@ var import_react3 = require("react");
|
|
|
894
928
|
var DRAFT_KEY = "form-page-builder:draft";
|
|
895
929
|
var INDEX_KEY = "form-page-builder:index";
|
|
896
930
|
var formKey = (id) => `form-page-builder:saved:${id}`;
|
|
931
|
+
var savedFormId = (key) => key.startsWith("form-page-builder:saved:") ? key.slice("form-page-builder:saved:".length) : null;
|
|
932
|
+
var CLIPBOARD_KEY = "form-page-builder:clipboard";
|
|
897
933
|
|
|
898
934
|
// src/hooks/usePersistence.ts
|
|
899
|
-
|
|
935
|
+
function snapshotOf(d) {
|
|
936
|
+
return JSON.stringify({ title: d.title, themeOverrides: d.themeOverrides, sections: d.sections });
|
|
937
|
+
}
|
|
900
938
|
function usePersistence({
|
|
901
939
|
storage,
|
|
902
940
|
autosave,
|
|
941
|
+
templateMax,
|
|
942
|
+
templateManage,
|
|
903
943
|
language,
|
|
904
944
|
chrome,
|
|
905
|
-
document,
|
|
945
|
+
document: document2,
|
|
906
946
|
initialDocument,
|
|
907
947
|
onLoadDocument,
|
|
908
948
|
onLoadThemeOverrides,
|
|
909
949
|
onTitleChange,
|
|
910
950
|
onNewForm,
|
|
951
|
+
onTemplateChange,
|
|
911
952
|
ensureActiveSection
|
|
912
953
|
}) {
|
|
913
954
|
const [currentFormId, setCurrentFormId] = (0, import_react3.useState)(null);
|
|
914
955
|
const [loadingDraft, setLoadingDraft] = (0, import_react3.useState)(true);
|
|
915
956
|
const [saveState, setSaveState] = (0, import_react3.useState)("idle");
|
|
957
|
+
const [templateState, setTemplateState] = (0, import_react3.useState)("idle");
|
|
916
958
|
const [savedForms, setSavedForms] = (0, import_react3.useState)([]);
|
|
917
959
|
const [saveAsPrompt, setSaveAsPrompt] = (0, import_react3.useState)(null);
|
|
960
|
+
const [notice, setNotice] = (0, import_react3.useState)(null);
|
|
918
961
|
const autosaveTimer = (0, import_react3.useRef)(null);
|
|
919
962
|
const hasLoadedOnce = (0, import_react3.useRef)(false);
|
|
963
|
+
const savedSnapshot = (0, import_react3.useRef)(null);
|
|
964
|
+
function flashSaved() {
|
|
965
|
+
setTemplateState("saved");
|
|
966
|
+
setTimeout(() => setTemplateState((s) => s === "saved" ? "idle" : s), 1500);
|
|
967
|
+
}
|
|
920
968
|
async function refreshLibrary() {
|
|
921
969
|
try {
|
|
922
970
|
const raw = await storage.get(INDEX_KEY);
|
|
923
971
|
const list = raw ? JSON.parse(raw) : [];
|
|
924
972
|
setSavedForms(list.sort((a, b) => b.updatedAt - a.updatedAt));
|
|
925
|
-
} catch
|
|
973
|
+
} catch {
|
|
926
974
|
setSavedForms([]);
|
|
927
975
|
}
|
|
928
976
|
}
|
|
@@ -946,12 +994,21 @@ function usePersistence({
|
|
|
946
994
|
onLoadDocument(doc);
|
|
947
995
|
onLoadThemeOverrides(doc.themeOverrides);
|
|
948
996
|
}
|
|
949
|
-
if (parsed.currentFormId)
|
|
997
|
+
if (parsed.currentFormId && templateManage) {
|
|
998
|
+
setCurrentFormId(parsed.currentFormId);
|
|
999
|
+
try {
|
|
1000
|
+
const tplRaw = await storage.get(formKey(parsed.currentFormId));
|
|
1001
|
+
const tplDoc = tplRaw ? migrateDocument(JSON.parse(tplRaw)) : null;
|
|
1002
|
+
savedSnapshot.current = tplDoc ? snapshotOf(tplDoc) : null;
|
|
1003
|
+
} catch {
|
|
1004
|
+
savedSnapshot.current = null;
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
950
1007
|
} else {
|
|
951
1008
|
ensureActiveSection();
|
|
952
1009
|
}
|
|
953
1010
|
}
|
|
954
|
-
} catch
|
|
1011
|
+
} catch {
|
|
955
1012
|
ensureActiveSection();
|
|
956
1013
|
} finally {
|
|
957
1014
|
setLoadingDraft(false);
|
|
@@ -966,86 +1023,125 @@ function usePersistence({
|
|
|
966
1023
|
if (autosaveTimer.current) clearTimeout(autosaveTimer.current);
|
|
967
1024
|
autosaveTimer.current = setTimeout(async () => {
|
|
968
1025
|
try {
|
|
969
|
-
await storage.set(DRAFT_KEY, JSON.stringify({ ...
|
|
1026
|
+
await storage.set(DRAFT_KEY, JSON.stringify({ version: DOCUMENT_VERSION, ...document2, currentFormId }));
|
|
970
1027
|
setSaveState("saved");
|
|
971
|
-
} catch
|
|
1028
|
+
} catch {
|
|
972
1029
|
setSaveState("error");
|
|
973
1030
|
}
|
|
974
1031
|
}, 600);
|
|
975
1032
|
return () => {
|
|
976
1033
|
if (autosaveTimer.current) clearTimeout(autosaveTimer.current);
|
|
977
1034
|
};
|
|
978
|
-
}, [
|
|
1035
|
+
}, [document2.title, document2.themeOverrides, document2.sections, currentFormId]);
|
|
979
1036
|
async function saveAs(name) {
|
|
980
|
-
if (
|
|
981
|
-
|
|
1037
|
+
if (!templateManage) return;
|
|
1038
|
+
if (savedForms.length >= templateMax) {
|
|
1039
|
+
setSaveAsPrompt(null);
|
|
1040
|
+
setNotice({ title: chrome.templatesLimitTitle, message: chrome.templatesLimitReached(templateMax) });
|
|
982
1041
|
return;
|
|
983
1042
|
}
|
|
984
1043
|
const id = genFormId();
|
|
985
1044
|
const now = Date.now();
|
|
1045
|
+
setTemplateState("saving");
|
|
986
1046
|
try {
|
|
987
1047
|
const newTitle = bi(name, "");
|
|
988
|
-
|
|
1048
|
+
const docToSave = { ...document2, title: newTitle };
|
|
1049
|
+
await storage.set(formKey(id), JSON.stringify({ version: DOCUMENT_VERSION, ...docToSave, id, updatedAt: now }));
|
|
989
1050
|
const next = [...savedForms, { id, title: name, updatedAt: now }];
|
|
990
1051
|
await storage.set(INDEX_KEY, JSON.stringify(next));
|
|
991
1052
|
setSavedForms(next);
|
|
992
1053
|
setCurrentFormId(id);
|
|
993
1054
|
onTitleChange(newTitle);
|
|
994
1055
|
setSaveAsPrompt(null);
|
|
995
|
-
|
|
996
|
-
|
|
1056
|
+
savedSnapshot.current = snapshotOf(docToSave);
|
|
1057
|
+
flashSaved();
|
|
1058
|
+
onTemplateChange?.({ id, title: name, source: "new" });
|
|
1059
|
+
} catch {
|
|
1060
|
+
setTemplateState("error");
|
|
997
1061
|
}
|
|
998
1062
|
}
|
|
999
1063
|
async function saveExisting() {
|
|
1064
|
+
if (!templateManage) return;
|
|
1000
1065
|
if (!currentFormId) {
|
|
1001
|
-
setSaveAsPrompt({ open: true, suggestedName: t(
|
|
1066
|
+
setSaveAsPrompt({ open: true, suggestedName: t(document2.title, language) });
|
|
1002
1067
|
return;
|
|
1003
1068
|
}
|
|
1004
1069
|
const now = Date.now();
|
|
1070
|
+
setTemplateState("saving");
|
|
1005
1071
|
try {
|
|
1006
|
-
await storage.set(formKey(currentFormId), JSON.stringify({ ...
|
|
1007
|
-
const next = savedForms.map((f) => f.id === currentFormId ? { ...f, title: t(
|
|
1072
|
+
await storage.set(formKey(currentFormId), JSON.stringify({ version: DOCUMENT_VERSION, ...document2, id: currentFormId, updatedAt: now }));
|
|
1073
|
+
const next = savedForms.map((f) => f.id === currentFormId ? { ...f, title: t(document2.title, "en"), updatedAt: now } : f);
|
|
1008
1074
|
setSavedForms(next);
|
|
1009
1075
|
await storage.set(INDEX_KEY, JSON.stringify(next));
|
|
1010
|
-
|
|
1011
|
-
|
|
1076
|
+
savedSnapshot.current = snapshotOf(document2);
|
|
1077
|
+
flashSaved();
|
|
1078
|
+
onTemplateChange?.({ id: currentFormId, title: t(document2.title, language), source: "saved" });
|
|
1079
|
+
} catch {
|
|
1080
|
+
setTemplateState("error");
|
|
1012
1081
|
}
|
|
1013
1082
|
}
|
|
1014
1083
|
async function loadForm(id) {
|
|
1084
|
+
setTemplateState("loading");
|
|
1015
1085
|
try {
|
|
1016
1086
|
const raw = await storage.get(formKey(id));
|
|
1017
|
-
if (!raw)
|
|
1087
|
+
if (!raw) {
|
|
1088
|
+
setTemplateState("error");
|
|
1089
|
+
return false;
|
|
1090
|
+
}
|
|
1018
1091
|
const doc = migrateDocument(JSON.parse(raw));
|
|
1019
|
-
if (!doc)
|
|
1092
|
+
if (!doc) {
|
|
1093
|
+
setTemplateState("error");
|
|
1094
|
+
return false;
|
|
1095
|
+
}
|
|
1020
1096
|
onLoadDocument(doc);
|
|
1021
1097
|
onLoadThemeOverrides(doc.themeOverrides);
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1098
|
+
const boundId = templateManage ? id : null;
|
|
1099
|
+
setCurrentFormId(boundId);
|
|
1100
|
+
savedSnapshot.current = boundId ? snapshotOf(doc) : null;
|
|
1101
|
+
setTemplateState("idle");
|
|
1102
|
+
onTemplateChange?.({ id, title: t(doc.title, language), source: "applied" });
|
|
1103
|
+
return true;
|
|
1104
|
+
} catch {
|
|
1105
|
+
setTemplateState("error");
|
|
1106
|
+
return false;
|
|
1025
1107
|
}
|
|
1026
1108
|
}
|
|
1027
1109
|
async function deleteForm(id) {
|
|
1110
|
+
if (!templateManage) return;
|
|
1111
|
+
const removed = savedForms.find((f) => f.id === id);
|
|
1028
1112
|
try {
|
|
1029
1113
|
await storage.delete(formKey(id));
|
|
1030
1114
|
const next = savedForms.filter((f) => f.id !== id);
|
|
1031
1115
|
setSavedForms(next);
|
|
1032
1116
|
await storage.set(INDEX_KEY, JSON.stringify(next));
|
|
1033
|
-
if (currentFormId === id)
|
|
1034
|
-
|
|
1117
|
+
if (currentFormId === id) {
|
|
1118
|
+
setCurrentFormId(null);
|
|
1119
|
+
savedSnapshot.current = null;
|
|
1120
|
+
}
|
|
1121
|
+
onTemplateChange?.({ id: currentFormId === id ? null : id, title: removed?.title ?? "", source: "deleted" });
|
|
1122
|
+
} catch {
|
|
1123
|
+
setTemplateState("error");
|
|
1035
1124
|
}
|
|
1036
1125
|
}
|
|
1037
1126
|
function newForm() {
|
|
1038
1127
|
onNewForm();
|
|
1039
1128
|
setCurrentFormId(null);
|
|
1129
|
+
savedSnapshot.current = null;
|
|
1130
|
+
setTemplateState("idle");
|
|
1040
1131
|
}
|
|
1132
|
+
const activeTemplate = currentFormId ? savedForms.find((f) => f.id === currentFormId) ?? null : null;
|
|
1133
|
+
const isTemplateDirty = savedSnapshot.current !== null && snapshotOf(document2) !== savedSnapshot.current;
|
|
1041
1134
|
function dismissSaveAsPrompt() {
|
|
1042
1135
|
setSaveAsPrompt(null);
|
|
1043
1136
|
}
|
|
1044
1137
|
return {
|
|
1045
1138
|
loadingDraft,
|
|
1046
1139
|
saveState,
|
|
1140
|
+
templateState,
|
|
1047
1141
|
savedForms,
|
|
1048
1142
|
currentFormId,
|
|
1143
|
+
activeTemplateTitle: activeTemplate?.title ?? null,
|
|
1144
|
+
isTemplateDirty,
|
|
1049
1145
|
saveAs,
|
|
1050
1146
|
saveExisting,
|
|
1051
1147
|
loadForm,
|
|
@@ -1053,15 +1149,64 @@ function usePersistence({
|
|
|
1053
1149
|
refreshLibrary,
|
|
1054
1150
|
newForm,
|
|
1055
1151
|
saveAsPrompt,
|
|
1056
|
-
dismissSaveAsPrompt
|
|
1152
|
+
dismissSaveAsPrompt,
|
|
1153
|
+
notice,
|
|
1154
|
+
dismissNotice: () => setNotice(null)
|
|
1057
1155
|
};
|
|
1058
1156
|
}
|
|
1059
1157
|
|
|
1060
|
-
// src/hooks/
|
|
1158
|
+
// src/hooks/useTemplateClipboard.ts
|
|
1061
1159
|
var import_react4 = require("react");
|
|
1160
|
+
var sameTabListeners = /* @__PURE__ */ new Set();
|
|
1161
|
+
function notifySameTab() {
|
|
1162
|
+
sameTabListeners.forEach((fn) => fn());
|
|
1163
|
+
}
|
|
1164
|
+
function readKey(key) {
|
|
1165
|
+
try {
|
|
1166
|
+
return typeof window !== "undefined" && window.localStorage ? window.localStorage.getItem(key) : null;
|
|
1167
|
+
} catch {
|
|
1168
|
+
return null;
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
function useTemplateClipboard(clipboardKey = CLIPBOARD_KEY) {
|
|
1172
|
+
const keyRef = (0, import_react4.useRef)(clipboardKey);
|
|
1173
|
+
keyRef.current = clipboardKey;
|
|
1174
|
+
const [hasClipboard, setHasClipboard] = (0, import_react4.useState)(false);
|
|
1175
|
+
const refresh = (0, import_react4.useCallback)(() => {
|
|
1176
|
+
setHasClipboard(parseTemplate(readKey(keyRef.current)) !== null);
|
|
1177
|
+
}, []);
|
|
1178
|
+
(0, import_react4.useEffect)(() => {
|
|
1179
|
+
refresh();
|
|
1180
|
+
const onStorage = (e) => {
|
|
1181
|
+
if (e.key === null || e.key === keyRef.current) refresh();
|
|
1182
|
+
};
|
|
1183
|
+
sameTabListeners.add(refresh);
|
|
1184
|
+
window.addEventListener("storage", onStorage);
|
|
1185
|
+
return () => {
|
|
1186
|
+
sameTabListeners.delete(refresh);
|
|
1187
|
+
window.removeEventListener("storage", onStorage);
|
|
1188
|
+
};
|
|
1189
|
+
}, [refresh]);
|
|
1190
|
+
const copyTemplate = (0, import_react4.useCallback)((document2) => {
|
|
1191
|
+
const str = serializeTemplate(document2);
|
|
1192
|
+
try {
|
|
1193
|
+
window.localStorage?.setItem(keyRef.current, str);
|
|
1194
|
+
} catch {
|
|
1195
|
+
}
|
|
1196
|
+
notifySameTab();
|
|
1197
|
+
refresh();
|
|
1198
|
+
}, [refresh]);
|
|
1199
|
+
const readTemplate = (0, import_react4.useCallback)(() => {
|
|
1200
|
+
return parseTemplate(readKey(keyRef.current));
|
|
1201
|
+
}, []);
|
|
1202
|
+
return { hasClipboard, copyTemplate, readTemplate };
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
// src/hooks/useDragReorder.ts
|
|
1206
|
+
var import_react5 = require("react");
|
|
1062
1207
|
function useDragReorder(reorderWithinSection) {
|
|
1063
|
-
const dragRef = (0,
|
|
1064
|
-
const [dragOverKey, setDragOverKey] = (0,
|
|
1208
|
+
const dragRef = (0, import_react5.useRef)({ sectionId: null, index: null });
|
|
1209
|
+
const [dragOverKey, setDragOverKey] = (0, import_react5.useState)(null);
|
|
1065
1210
|
function getDropZoneHandlers(sectionId, index) {
|
|
1066
1211
|
const dragKey = `${sectionId}:${index}`;
|
|
1067
1212
|
return {
|
|
@@ -1095,7 +1240,7 @@ var import_lucide_react2 = require("lucide-react");
|
|
|
1095
1240
|
var styles = {
|
|
1096
1241
|
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" },
|
|
1097
1242
|
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" },
|
|
1098
|
-
toolbarLeft: { display: "flex", alignItems: "center", gap: 10 },
|
|
1243
|
+
toolbarLeft: { display: "flex", alignItems: "center", gap: 10, minWidth: 0, flexShrink: 1 },
|
|
1099
1244
|
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" },
|
|
1100
1245
|
titleInput: { border: "none", outline: "none", fontSize: 14, fontWeight: 600, background: "transparent", color: "var(--fb-ink)", padding: "4px 6px", borderRadius: 6, minWidth: 160 },
|
|
1101
1246
|
toolbarRight: { display: "flex", alignItems: "center", gap: 6, flexWrap: "wrap" },
|
|
@@ -1105,6 +1250,8 @@ var styles = {
|
|
|
1105
1250
|
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 },
|
|
1106
1251
|
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 },
|
|
1107
1252
|
saveStatus: { display: "flex", alignItems: "center", gap: 5, fontSize: 12, color: "var(--fb-muted)", minWidth: 60 },
|
|
1253
|
+
templateTag: { display: "flex", alignItems: "center", gap: 6, fontSize: 12, color: "var(--fb-muted)", minWidth: 0, flexShrink: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
1254
|
+
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)" },
|
|
1108
1255
|
countBadge: { fontSize: 10.5, fontWeight: 700, background: "var(--fb-primary-soft)", color: "var(--fb-primary)", borderRadius: 9, padding: "1px 6px", marginLeft: 2 },
|
|
1109
1256
|
loadingScreen: { flex: 1, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", color: "var(--fb-muted)" },
|
|
1110
1257
|
libraryBody: { padding: "var(--fb-space-panel)", overflowY: "auto", display: "flex", flexDirection: "column", gap: 4 },
|
|
@@ -1241,21 +1388,36 @@ function Toolbar({
|
|
|
1241
1388
|
languages,
|
|
1242
1389
|
mode,
|
|
1243
1390
|
saveState,
|
|
1391
|
+
templateState,
|
|
1392
|
+
activeTemplateTitle,
|
|
1393
|
+
templateDirty,
|
|
1244
1394
|
chrome,
|
|
1245
1395
|
features,
|
|
1246
1396
|
savedFormsCount,
|
|
1397
|
+
clipboardReady,
|
|
1398
|
+
templateCopied,
|
|
1247
1399
|
onTitleChange,
|
|
1248
1400
|
onLanguageChange,
|
|
1249
1401
|
onModeChange,
|
|
1250
1402
|
onNewForm,
|
|
1251
1403
|
onOpenLibrary,
|
|
1252
1404
|
onSaveExisting,
|
|
1253
|
-
onOpenJson
|
|
1405
|
+
onOpenJson,
|
|
1406
|
+
onCopyTemplate,
|
|
1407
|
+
onPasteTemplate
|
|
1254
1408
|
}) {
|
|
1255
1409
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: styles.toolbar, children: [
|
|
1256
1410
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: styles.toolbarLeft, children: [
|
|
1257
1411
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: styles.logoMark, children: "FB" }),
|
|
1258
|
-
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) })
|
|
1412
|
+
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) }),
|
|
1413
|
+
templateState === "loading" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: styles.saveStatus, children: [
|
|
1414
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react2.Loader2, { size: 12, className: "spin" }),
|
|
1415
|
+
" ",
|
|
1416
|
+
chrome.templateLoading
|
|
1417
|
+
] }) : activeTemplateTitle && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "fb-template-tag", style: styles.templateTag, title: chrome.templateLabel(activeTemplateTitle), children: [
|
|
1418
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { overflow: "hidden", textOverflow: "ellipsis" }, children: chrome.templateLabel(activeTemplateTitle) }),
|
|
1419
|
+
templateDirty && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: styles.currentBadge, children: chrome.templateEdited })
|
|
1420
|
+
] })
|
|
1259
1421
|
] }),
|
|
1260
1422
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: styles.toolbarRight, children: [
|
|
1261
1423
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: styles.saveStatus, children: [
|
|
@@ -1288,20 +1450,20 @@ function Toolbar({
|
|
|
1288
1450
|
chrome.preview
|
|
1289
1451
|
] })
|
|
1290
1452
|
] }),
|
|
1291
|
-
(features.newForm || features.templates) && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: styles.toolbarDivider }),
|
|
1453
|
+
(features.newForm || features.templates.enabled) && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: styles.toolbarDivider }),
|
|
1292
1454
|
features.newForm && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("button", { style: styles.ghostBtn, onClick: onNewForm, title: chrome.startNewForm, children: [
|
|
1293
1455
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react2.FilePlus2, { size: 14 }),
|
|
1294
1456
|
" ",
|
|
1295
1457
|
chrome.newForm
|
|
1296
1458
|
] }),
|
|
1297
|
-
features.templates && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
1459
|
+
features.templates.enabled && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
1298
1460
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("button", { style: styles.ghostBtn, onClick: onOpenLibrary, title: chrome.openTemplatesTitle, children: [
|
|
1299
1461
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react2.FolderOpen, { size: 14 }),
|
|
1300
1462
|
" ",
|
|
1301
1463
|
chrome.templates,
|
|
1302
1464
|
savedFormsCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: styles.countBadge, children: savedFormsCount })
|
|
1303
1465
|
] }),
|
|
1304
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("button", { style: styles.primaryBtn, onClick: onSaveExisting, title: chrome.saveToLibraryTitle, children: [
|
|
1466
|
+
features.templates.manage && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("button", { style: styles.primaryBtn, onClick: onSaveExisting, title: chrome.saveToLibraryTitle, children: [
|
|
1305
1467
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react2.Save, { size: 14 }),
|
|
1306
1468
|
" ",
|
|
1307
1469
|
chrome.save
|
|
@@ -1311,13 +1473,17 @@ function Toolbar({
|
|
|
1311
1473
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react2.Code2, { size: 14 }),
|
|
1312
1474
|
" ",
|
|
1313
1475
|
chrome.viewJson
|
|
1476
|
+
] }),
|
|
1477
|
+
features.templateClipboard && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
1478
|
+
/* @__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 }) }),
|
|
1479
|
+
/* @__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 }) })
|
|
1314
1480
|
] })
|
|
1315
1481
|
] })
|
|
1316
1482
|
] });
|
|
1317
1483
|
}
|
|
1318
1484
|
|
|
1319
1485
|
// src/components/Palette.tsx
|
|
1320
|
-
var
|
|
1486
|
+
var import_react6 = require("react");
|
|
1321
1487
|
var import_lucide_react5 = require("lucide-react");
|
|
1322
1488
|
|
|
1323
1489
|
// src/components/Segmented.tsx
|
|
@@ -1415,7 +1581,7 @@ function Palette({
|
|
|
1415
1581
|
resetTheme,
|
|
1416
1582
|
fieldCount
|
|
1417
1583
|
}) {
|
|
1418
|
-
const [tab, setTab] = (0,
|
|
1584
|
+
const [tab, setTab] = (0, import_react6.useState)("blocks");
|
|
1419
1585
|
const contentTypes = CONTENT_TYPES.filter((f) => isContentBlockEnabled(features, f.type));
|
|
1420
1586
|
const formTypes = FORM_TYPES.filter((f) => isFieldTypeEnabled(features, f.type));
|
|
1421
1587
|
const atFieldCap = typeof features.maxFields === "number" && fieldCount >= features.maxFields;
|
|
@@ -1704,6 +1870,8 @@ function FieldTicket({
|
|
|
1704
1870
|
{
|
|
1705
1871
|
...dropZoneHandlers,
|
|
1706
1872
|
onClick: onSelect,
|
|
1873
|
+
className: `fb-field fb-field--${field.type} fb-field--${field.id}`,
|
|
1874
|
+
"data-field-id": field.id,
|
|
1707
1875
|
style: {
|
|
1708
1876
|
...styles.ticket,
|
|
1709
1877
|
flex: `1 1 calc(${widthPct} - 10px)`,
|
|
@@ -1800,7 +1968,7 @@ function SectionCard({
|
|
|
1800
1968
|
),
|
|
1801
1969
|
section.collapsed && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: styles.miniBadge, children: chrome.fieldsCount(section.fields.length) }),
|
|
1802
1970
|
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: styles.sectionHeaderActions, onClick: (e) => e.stopPropagation(), children: [
|
|
1803
|
-
features.
|
|
1971
|
+
features.sectionBackground && !section.collapsed && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: styles.swatchRow, children: [
|
|
1804
1972
|
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")),
|
|
1805
1973
|
/* @__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 })
|
|
1806
1974
|
] }),
|
|
@@ -2121,7 +2289,7 @@ function InspectorBody({ selected, language, chrome, features, onUpdateField, on
|
|
|
2121
2289
|
}
|
|
2122
2290
|
|
|
2123
2291
|
// src/components/PreviewPane.tsx
|
|
2124
|
-
var
|
|
2292
|
+
var import_react7 = require("react");
|
|
2125
2293
|
var import_lucide_react14 = require("lucide-react");
|
|
2126
2294
|
|
|
2127
2295
|
// src/lib/validate.ts
|
|
@@ -2170,10 +2338,10 @@ function collectFieldValues(fields) {
|
|
|
2170
2338
|
// src/components/PreviewPane.tsx
|
|
2171
2339
|
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
2172
2340
|
function PreviewPane({ title, sections, onFieldChange, language, strings, chrome, baseMaxWidth, features, onSubmit }) {
|
|
2173
|
-
const [device, setDevice] = (0,
|
|
2174
|
-
const [errors, setErrors] = (0,
|
|
2175
|
-
const [submitted, setSubmitted] = (0,
|
|
2176
|
-
const [attemptedButtonId, setAttemptedButtonId] = (0,
|
|
2341
|
+
const [device, setDevice] = (0, import_react7.useState)("laptop");
|
|
2342
|
+
const [errors, setErrors] = (0, import_react7.useState)({});
|
|
2343
|
+
const [submitted, setSubmitted] = (0, import_react7.useState)(null);
|
|
2344
|
+
const [attemptedButtonId, setAttemptedButtonId] = (0, import_react7.useState)(null);
|
|
2177
2345
|
const deviceOptions = buildDeviceOptions(baseMaxWidth, chrome);
|
|
2178
2346
|
const maxWidth = (deviceOptions.find((d) => d.value === device) || deviceOptions[0]).maxWidth;
|
|
2179
2347
|
const allFields = sections.flatMap((s) => s.fields);
|
|
@@ -2233,16 +2401,16 @@ function PreviewPane({ title, sections, onFieldChange, language, strings, chrome
|
|
|
2233
2401
|
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { style: styles.previewWrap, children: [
|
|
2234
2402
|
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { style: { width: "100%", maxWidth, display: "flex", flexDirection: "column", alignItems: "center" }, children: [
|
|
2235
2403
|
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 }) }),
|
|
2236
|
-
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { style: { ...styles.previewCard, maxWidth, width: "100%" }, children: [
|
|
2237
|
-
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("h2", { style: styles.previewTitle, children: t(title, language) }),
|
|
2404
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "fb-form", style: { ...styles.previewCard, maxWidth, width: "100%" }, children: [
|
|
2405
|
+
features.formTitle && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("h2", { style: styles.previewTitle, children: t(title, language) }),
|
|
2238
2406
|
allFields.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { style: { color: "var(--fb-muted)", fontSize: 14 }, children: strings.addFieldsHint }),
|
|
2239
2407
|
sections.map((section) => {
|
|
2240
|
-
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { style: { background: section.background || "transparent", padding: section.background ? 16 : 0, borderRadius: section.background ? 10 : 0, marginBottom: "var(--fb-space-section)" }, children: [
|
|
2408
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "fb-section", "data-section-id": section.id, style: { background: section.background || "transparent", padding: section.background ? 16 : 0, borderRadius: section.background ? 10 : 0, marginBottom: "var(--fb-space-section)" }, children: [
|
|
2241
2409
|
t(section.title, language) && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("h3", { style: styles.sectionRuntimeTitle, children: t(section.title, language) }),
|
|
2242
2410
|
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { style: styles.previewGrid, children: section.fields.map((field) => {
|
|
2243
2411
|
const width = effectiveWidth(field.width, device);
|
|
2244
2412
|
const flexBasis = `1 1 calc(${WIDTH_PERCENT[width]} - 14px)`;
|
|
2245
|
-
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { style: { flex: flexBasis, minWidth: 0, alignSelf: ALIGN_MAP[field.verticalAlign || "top"] }, children: [
|
|
2413
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: `fb-field fb-field--${field.type} fb-field--${field.id}`, "data-field-id": field.id, style: { flex: flexBasis, minWidth: 0, alignSelf: ALIGN_MAP[field.verticalAlign || "top"] }, children: [
|
|
2246
2414
|
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(FieldBlock, { field, lang: language, onFieldChange, strings, chrome, error: errors[field.id], onButtonAction: handleButtonAction }),
|
|
2247
2415
|
field.type === "button" && field.id === attemptedButtonId && Object.keys(errors).length > 0 && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("p", { style: styles.formErrorNote, children: [
|
|
2248
2416
|
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react14.CircleAlert, { size: 13 }),
|
|
@@ -2278,17 +2446,66 @@ function PreviewPane({ title, sections, onFieldChange, language, strings, chrome
|
|
|
2278
2446
|
|
|
2279
2447
|
// src/components/modals/JsonModal.tsx
|
|
2280
2448
|
var import_lucide_react15 = require("lucide-react");
|
|
2449
|
+
|
|
2450
|
+
// src/hooks/useModalA11y.ts
|
|
2451
|
+
var import_react8 = require("react");
|
|
2452
|
+
var FOCUSABLE = 'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])';
|
|
2453
|
+
function useModalA11y(onClose) {
|
|
2454
|
+
const panelRef = (0, import_react8.useRef)(null);
|
|
2455
|
+
const onCloseRef = (0, import_react8.useRef)(onClose);
|
|
2456
|
+
onCloseRef.current = onClose;
|
|
2457
|
+
(0, import_react8.useEffect)(() => {
|
|
2458
|
+
const opener = document.activeElement;
|
|
2459
|
+
const panel = panelRef.current;
|
|
2460
|
+
const focusables = () => Array.from(panel?.querySelectorAll(FOCUSABLE) ?? []);
|
|
2461
|
+
if (panel && !panel.contains(document.activeElement)) {
|
|
2462
|
+
(panel.querySelector("[autofocus]") ?? focusables()[0] ?? panel).focus();
|
|
2463
|
+
}
|
|
2464
|
+
const onKeyDown = (e) => {
|
|
2465
|
+
if (e.key === "Escape") {
|
|
2466
|
+
e.stopPropagation();
|
|
2467
|
+
onCloseRef.current();
|
|
2468
|
+
return;
|
|
2469
|
+
}
|
|
2470
|
+
if (e.key !== "Tab") return;
|
|
2471
|
+
const items = focusables();
|
|
2472
|
+
if (items.length === 0) {
|
|
2473
|
+
e.preventDefault();
|
|
2474
|
+
return;
|
|
2475
|
+
}
|
|
2476
|
+
const first = items[0];
|
|
2477
|
+
const last = items[items.length - 1];
|
|
2478
|
+
const active = document.activeElement;
|
|
2479
|
+
if (e.shiftKey && (active === first || !panel?.contains(active))) {
|
|
2480
|
+
e.preventDefault();
|
|
2481
|
+
last.focus();
|
|
2482
|
+
} else if (!e.shiftKey && active === last) {
|
|
2483
|
+
e.preventDefault();
|
|
2484
|
+
first.focus();
|
|
2485
|
+
}
|
|
2486
|
+
};
|
|
2487
|
+
document.addEventListener("keydown", onKeyDown, true);
|
|
2488
|
+
return () => {
|
|
2489
|
+
document.removeEventListener("keydown", onKeyDown, true);
|
|
2490
|
+
opener?.focus?.();
|
|
2491
|
+
};
|
|
2492
|
+
}, []);
|
|
2493
|
+
return panelRef;
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
// src/components/modals/JsonModal.tsx
|
|
2281
2497
|
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
2282
2498
|
function JsonModal({ chrome, jsonString, copied, onCopy, onClose }) {
|
|
2283
|
-
|
|
2499
|
+
const panelRef = useModalA11y(onClose);
|
|
2500
|
+
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: [
|
|
2284
2501
|
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { style: styles.modalHeader, children: [
|
|
2285
2502
|
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { style: { fontWeight: 600, fontSize: 14 }, children: chrome.formJson }),
|
|
2286
2503
|
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { style: { display: "flex", gap: 8 }, children: [
|
|
2287
|
-
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("button", { style: styles.ghostBtn, onClick: onCopy, children: [
|
|
2504
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("button", { type: "button", style: styles.ghostBtn, onClick: onCopy, children: [
|
|
2288
2505
|
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 }),
|
|
2289
2506
|
copied ? chrome.copied : chrome.copy
|
|
2290
2507
|
] }),
|
|
2291
|
-
/* @__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 }) })
|
|
2508
|
+
/* @__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 }) })
|
|
2292
2509
|
] })
|
|
2293
2510
|
] }),
|
|
2294
2511
|
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("pre", { style: styles.jsonPre, children: jsonString })
|
|
@@ -2298,55 +2515,97 @@ function JsonModal({ chrome, jsonString, copied, onCopy, onClose }) {
|
|
|
2298
2515
|
// src/components/modals/TemplatesModal.tsx
|
|
2299
2516
|
var import_lucide_react16 = require("lucide-react");
|
|
2300
2517
|
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
2301
|
-
function TemplatesModal({ chrome, savedForms, currentFormId, onOpen, onDelete, onClose }) {
|
|
2302
|
-
|
|
2518
|
+
function TemplatesModal({ chrome, savedForms, currentFormId, manage, templateState, onOpen, onDelete, onClose }) {
|
|
2519
|
+
const busy = templateState === "loading";
|
|
2520
|
+
const panelRef = useModalA11y(onClose);
|
|
2521
|
+
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: [
|
|
2303
2522
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: styles.modalHeader, children: [
|
|
2304
2523
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: { fontWeight: 600, fontSize: 14 }, children: chrome.templates }),
|
|
2305
|
-
/* @__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 }) })
|
|
2524
|
+
/* @__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 }) })
|
|
2306
2525
|
] }),
|
|
2307
|
-
/* @__PURE__ */ (0, import_jsx_runtime16.
|
|
2308
|
-
/* @__PURE__ */ (0, import_jsx_runtime16.
|
|
2309
|
-
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style:
|
|
2310
|
-
|
|
2311
|
-
|
|
2526
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: styles.libraryBody, children: [
|
|
2527
|
+
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: [
|
|
2528
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { minWidth: 0 }, children: [
|
|
2529
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: styles.libraryRowTitle, children: [
|
|
2530
|
+
f.title,
|
|
2531
|
+
f.id === currentFormId && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: styles.currentBadge, children: chrome.current })
|
|
2532
|
+
] }),
|
|
2533
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: styles.libraryRowMeta, children: [
|
|
2534
|
+
chrome.updated,
|
|
2535
|
+
" ",
|
|
2536
|
+
new Date(f.updatedAt).toLocaleString()
|
|
2537
|
+
] })
|
|
2312
2538
|
] }),
|
|
2313
|
-
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style:
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2539
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { display: "flex", gap: 6, flexShrink: 0 }, children: [
|
|
2540
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("button", { type: "button", style: styles.ghostBtn, disabled: busy, onClick: () => onOpen(f.id), children: [
|
|
2541
|
+
busy && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react16.Loader2, { size: 12, className: "spin" }),
|
|
2542
|
+
" ",
|
|
2543
|
+
manage ? chrome.open : chrome.applyTemplate
|
|
2544
|
+
] }),
|
|
2545
|
+
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 }) })
|
|
2317
2546
|
] })
|
|
2318
|
-
] }),
|
|
2319
|
-
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { display: "flex", gap:
|
|
2320
|
-
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
2321
|
-
|
|
2547
|
+
] }, f.id)),
|
|
2548
|
+
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: [
|
|
2549
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react16.AlertCircle, { size: 12 }),
|
|
2550
|
+
" ",
|
|
2551
|
+
chrome.templateLoadFailed
|
|
2322
2552
|
] })
|
|
2323
|
-
] }
|
|
2553
|
+
] })
|
|
2324
2554
|
] }) });
|
|
2325
2555
|
}
|
|
2326
2556
|
|
|
2327
2557
|
// src/components/modals/SaveAsModal.tsx
|
|
2328
|
-
var
|
|
2558
|
+
var import_react9 = require("react");
|
|
2329
2559
|
var import_lucide_react17 = require("lucide-react");
|
|
2330
2560
|
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
2331
|
-
function SaveAsModal({ chrome, suggestedName, onSave, onClose }) {
|
|
2332
|
-
const [name, setName] = (0,
|
|
2333
|
-
|
|
2561
|
+
function SaveAsModal({ chrome, suggestedName, templateState, onSave, onClose }) {
|
|
2562
|
+
const [name, setName] = (0, import_react9.useState)(suggestedName);
|
|
2563
|
+
const saving = templateState === "saving";
|
|
2564
|
+
const canSave = !!name.trim() && !saving;
|
|
2565
|
+
const panelRef = useModalA11y(onClose);
|
|
2566
|
+
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: [
|
|
2334
2567
|
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { style: styles.modalHeader, children: [
|
|
2335
2568
|
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { style: { fontWeight: 600, fontSize: 14 }, children: chrome.saveForm }),
|
|
2336
|
-
/* @__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 }) })
|
|
2569
|
+
/* @__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 }) })
|
|
2337
2570
|
] }),
|
|
2338
2571
|
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { style: { padding: 16 }, children: [
|
|
2339
2572
|
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("label", { style: styles.propLabel, children: chrome.formName }),
|
|
2340
|
-
/* @__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" &&
|
|
2341
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("button", { style: { ...styles.primaryBtn, width: "100%", justifyContent: "center", marginTop: 14 }, disabled: !
|
|
2342
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react17.Save, { size: 14 }),
|
|
2573
|
+
/* @__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()) }),
|
|
2574
|
+
/* @__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: [
|
|
2575
|
+
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 }),
|
|
2343
2576
|
" ",
|
|
2344
2577
|
chrome.saveToLibrary
|
|
2578
|
+
] }),
|
|
2579
|
+
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: [
|
|
2580
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react17.AlertCircle, { size: 12 }),
|
|
2581
|
+
" ",
|
|
2582
|
+
chrome.templateSaveFailed
|
|
2345
2583
|
] })
|
|
2346
2584
|
] })
|
|
2347
2585
|
] }) });
|
|
2348
2586
|
}
|
|
2349
2587
|
|
|
2588
|
+
// src/components/modals/ConfirmModal.tsx
|
|
2589
|
+
var import_lucide_react18 = require("lucide-react");
|
|
2590
|
+
var import_jsx_runtime18 = require("react/jsx-runtime");
|
|
2591
|
+
function ConfirmModal({ chrome, title, message, onConfirm, confirmLabel, tone = "default", onClose }) {
|
|
2592
|
+
const panelRef = useModalA11y(onClose);
|
|
2593
|
+
const confirmStyle = tone === "danger" ? { ...styles.primaryBtn, background: "var(--fb-danger)", borderColor: "var(--fb-danger)" } : styles.primaryBtn;
|
|
2594
|
+
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: [
|
|
2595
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: styles.modalHeader, children: [
|
|
2596
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { style: { fontWeight: 600, fontSize: 14 }, children: title }),
|
|
2597
|
+
/* @__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 }) })
|
|
2598
|
+
] }),
|
|
2599
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { padding: 16 }, children: [
|
|
2600
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { style: { margin: 0, fontSize: 13, lineHeight: 1.5, color: "var(--fb-ink)" }, children: message }),
|
|
2601
|
+
/* @__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: [
|
|
2602
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", autoFocus: true, style: styles.ghostBtn, onClick: onClose, children: chrome.cancel }),
|
|
2603
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", style: confirmStyle, onClick: onConfirm, children: confirmLabel ?? chrome.confirm })
|
|
2604
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", autoFocus: true, style: styles.primaryBtn, onClick: onClose, children: chrome.dismiss }) })
|
|
2605
|
+
] })
|
|
2606
|
+
] }) });
|
|
2607
|
+
}
|
|
2608
|
+
|
|
2350
2609
|
// src/styles/globalCss.ts
|
|
2351
2610
|
var css = `
|
|
2352
2611
|
* { box-sizing: border-box; }
|
|
@@ -2354,20 +2613,14 @@ var css = `
|
|
|
2354
2613
|
input, textarea, select { font-family: inherit; }
|
|
2355
2614
|
@keyframes fb-spin { to { transform: rotate(360deg); } }
|
|
2356
2615
|
.spin { animation: fb-spin 0.8s linear infinite; }
|
|
2357
|
-
/*
|
|
2358
|
-
than growing past whatever space its host container gives it; the
|
|
2359
|
-
second declaration upgrades to the dynamic viewport unit on browsers
|
|
2360
|
-
that support it (mobile browser chrome resizing the viewport), and is
|
|
2361
|
-
simply ignored by ones that don't. */
|
|
2616
|
+
/* Cap at viewport height so the widget scrolls internally; dvh upgrade is ignored where unsupported. */
|
|
2362
2617
|
.fb-root { max-height: 100vh; max-height: 100dvh; }
|
|
2363
2618
|
|
|
2364
|
-
/*
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
buttons, tracked by .fb-work-area's data-mobile-panel attribute,
|
|
2370
|
-
and dismissible via .fb-mobile-backdrop. */
|
|
2619
|
+
/* Toolbar's active-template label is nice-to-have \u2014 drop it first when width is tight. */
|
|
2620
|
+
@media (max-width: 600px) { .fb-template-tag { display: none; } }
|
|
2621
|
+
|
|
2622
|
+
/* Under 720px: Palette/Inspector become drawers over Canvas, toggled by .fb-mobile-bar,
|
|
2623
|
+
tracked by .fb-work-area[data-mobile-panel], dismissed via .fb-mobile-backdrop. */
|
|
2371
2624
|
.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; }
|
|
2372
2625
|
.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; }
|
|
2373
2626
|
.fb-mobile-btn[aria-pressed="true"] { border-color: var(--fb-primary); background: var(--fb-primary-soft); color: var(--fb-primary); }
|
|
@@ -2393,8 +2646,8 @@ var css = `
|
|
|
2393
2646
|
`;
|
|
2394
2647
|
|
|
2395
2648
|
// src/FormBuilder.tsx
|
|
2396
|
-
var
|
|
2397
|
-
var FormBuilder = (0,
|
|
2649
|
+
var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
2650
|
+
var FormBuilder = (0, import_react10.forwardRef)(function FormBuilder2({
|
|
2398
2651
|
theme: themeOverrideProp,
|
|
2399
2652
|
language: languageOverride,
|
|
2400
2653
|
languages = DEFAULT_LANGUAGES,
|
|
@@ -2405,31 +2658,38 @@ var FormBuilder = (0, import_react8.forwardRef)(function FormBuilder2({
|
|
|
2405
2658
|
onSubmit,
|
|
2406
2659
|
initialDocument,
|
|
2407
2660
|
initialMode,
|
|
2408
|
-
onModeChange
|
|
2661
|
+
onModeChange,
|
|
2662
|
+
onTemplateChange,
|
|
2663
|
+
templateClipboardKey
|
|
2409
2664
|
}, ref) {
|
|
2410
2665
|
const features = resolveFeatures(featuresProp);
|
|
2411
2666
|
const storage = storageProp ?? localStorageAdapter;
|
|
2412
2667
|
const { theme, updateThemeColor, updateThemeLayout, resetTheme, replaceThemeOverrides, themeOverrides } = useTheme(themeOverrideProp);
|
|
2413
|
-
const [language, setLanguage] = (0,
|
|
2668
|
+
const [language, setLanguage] = (0, import_react10.useState)(languageOverride || languages[0]?.code || "en");
|
|
2414
2669
|
const strings = { ...DEFAULT_STRINGS[language] || DEFAULT_STRINGS.en, ...stringsOverride && stringsOverride[language] || {} };
|
|
2415
2670
|
const chrome = { ...CHROME[language] || CHROME.en, ...chromeOverride && chromeOverride[language] || {} };
|
|
2416
2671
|
const doc = useFormDocument({ language, chrome });
|
|
2417
2672
|
const drag = useDragReorder(doc.reorderWithinSection);
|
|
2418
|
-
const [mode, setModeState] = (0,
|
|
2673
|
+
const [mode, setModeState] = (0, import_react10.useState)(initialMode ?? "build");
|
|
2419
2674
|
const setMode = (next) => {
|
|
2420
2675
|
setModeState(next);
|
|
2421
2676
|
onModeChange?.(next);
|
|
2422
2677
|
};
|
|
2423
|
-
(0,
|
|
2678
|
+
(0, import_react10.useEffect)(() => {
|
|
2424
2679
|
onModeChange?.(mode);
|
|
2425
2680
|
}, []);
|
|
2426
|
-
const [showJson, setShowJson] = (0,
|
|
2427
|
-
const [showLibrary, setShowLibrary] = (0,
|
|
2428
|
-
const [copied, setCopied] = (0,
|
|
2429
|
-
const [
|
|
2681
|
+
const [showJson, setShowJson] = (0, import_react10.useState)(false);
|
|
2682
|
+
const [showLibrary, setShowLibrary] = (0, import_react10.useState)(false);
|
|
2683
|
+
const [copied, setCopied] = (0, import_react10.useState)(false);
|
|
2684
|
+
const [templateCopied, setTemplateCopied] = (0, import_react10.useState)(false);
|
|
2685
|
+
const [pastePrompt, setPastePrompt] = (0, import_react10.useState)(false);
|
|
2686
|
+
const clipboard = useTemplateClipboard(templateClipboardKey);
|
|
2687
|
+
const [mobilePanel, setMobilePanel] = (0, import_react10.useState)("none");
|
|
2430
2688
|
const persistence = usePersistence({
|
|
2431
2689
|
storage,
|
|
2432
2690
|
autosave: features.autosave,
|
|
2691
|
+
templateMax: features.templates.max,
|
|
2692
|
+
templateManage: features.templates.manage,
|
|
2433
2693
|
language,
|
|
2434
2694
|
chrome,
|
|
2435
2695
|
document: { title: doc.title, themeOverrides, sections: doc.sections },
|
|
@@ -2441,16 +2701,17 @@ var FormBuilder = (0, import_react8.forwardRef)(function FormBuilder2({
|
|
|
2441
2701
|
doc.resetToBlank();
|
|
2442
2702
|
resetTheme();
|
|
2443
2703
|
},
|
|
2704
|
+
onTemplateChange,
|
|
2444
2705
|
ensureActiveSection: () => doc.setActiveSectionId((prev) => prev ?? doc.sections[0]?.id ?? null)
|
|
2445
2706
|
});
|
|
2446
|
-
const jsonDoc = {
|
|
2447
|
-
version:
|
|
2707
|
+
const jsonDoc = (0, import_react10.useMemo)(() => ({
|
|
2708
|
+
version: DOCUMENT_VERSION,
|
|
2448
2709
|
title: doc.title,
|
|
2449
2710
|
theme,
|
|
2450
2711
|
themeOverrides,
|
|
2451
2712
|
sections: doc.sections.map((s) => ({ id: s.id, title: s.title, background: s.background, collapsed: s.collapsed, fields: s.fields }))
|
|
2452
|
-
};
|
|
2453
|
-
const jsonString = JSON.stringify(jsonDoc, null, 2);
|
|
2713
|
+
}), [doc.title, doc.sections, theme, themeOverrides]);
|
|
2714
|
+
const jsonString = (0, import_react10.useMemo)(() => JSON.stringify(jsonDoc, null, 2), [jsonDoc]);
|
|
2454
2715
|
function copyJson() {
|
|
2455
2716
|
navigator.clipboard.writeText(jsonString).then(() => {
|
|
2456
2717
|
setCopied(true);
|
|
@@ -2458,15 +2719,28 @@ var FormBuilder = (0, import_react8.forwardRef)(function FormBuilder2({
|
|
|
2458
2719
|
}).catch(() => {
|
|
2459
2720
|
});
|
|
2460
2721
|
}
|
|
2461
|
-
|
|
2722
|
+
function applyDocument(migrated) {
|
|
2723
|
+
if (!migrated) return false;
|
|
2724
|
+
doc.loadDocument(migrated);
|
|
2725
|
+
replaceThemeOverrides(migrated.themeOverrides);
|
|
2726
|
+
return true;
|
|
2727
|
+
}
|
|
2728
|
+
function copyTemplate() {
|
|
2729
|
+
clipboard.copyTemplate(jsonDoc);
|
|
2730
|
+
setTemplateCopied(true);
|
|
2731
|
+
setTimeout(() => setTemplateCopied(false), 1500);
|
|
2732
|
+
}
|
|
2733
|
+
function pasteTemplate() {
|
|
2734
|
+
if (clipboard.readTemplate()) setPastePrompt(true);
|
|
2735
|
+
}
|
|
2736
|
+
(0, import_react10.useImperativeHandle)(ref, () => ({
|
|
2462
2737
|
getDocument: () => jsonDoc,
|
|
2463
2738
|
exportJson: () => jsonString,
|
|
2464
2739
|
loadDocument: (raw) => {
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
}
|
|
2740
|
+
applyDocument(migrateDocument(raw));
|
|
2741
|
+
},
|
|
2742
|
+
getTemplate: () => ({ __fpb: "template", v: TEMPLATE_FORMAT, document: jsonDoc }),
|
|
2743
|
+
loadTemplate: (input) => applyDocument(parseTemplate(typeof input === "string" ? input : JSON.stringify(input)))
|
|
2470
2744
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2471
2745
|
}), [jsonDoc, jsonString]);
|
|
2472
2746
|
const fieldCount = doc.sections.reduce((n, s) => n + s.fields.filter((f) => !getMeta(f.type).isContent).length, 0);
|
|
@@ -2493,9 +2767,9 @@ var FormBuilder = (0, import_react8.forwardRef)(function FormBuilder2({
|
|
|
2493
2767
|
"--fb-space-panel": `${theme.layout.panelPadding}px`,
|
|
2494
2768
|
"--fb-space-ticket": `${theme.layout.ticketPadding}px`
|
|
2495
2769
|
};
|
|
2496
|
-
return /* @__PURE__ */ (0,
|
|
2497
|
-
/* @__PURE__ */ (0,
|
|
2498
|
-
/* @__PURE__ */ (0,
|
|
2770
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "fb-root", style: rootStyle, children: [
|
|
2771
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("style", { children: css }),
|
|
2772
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
2499
2773
|
Toolbar,
|
|
2500
2774
|
{
|
|
2501
2775
|
title: doc.title,
|
|
@@ -2503,24 +2777,31 @@ var FormBuilder = (0, import_react8.forwardRef)(function FormBuilder2({
|
|
|
2503
2777
|
languages,
|
|
2504
2778
|
mode,
|
|
2505
2779
|
saveState: persistence.saveState,
|
|
2780
|
+
templateState: persistence.templateState,
|
|
2781
|
+
activeTemplateTitle: persistence.activeTemplateTitle,
|
|
2782
|
+
templateDirty: persistence.isTemplateDirty,
|
|
2506
2783
|
chrome,
|
|
2507
2784
|
features,
|
|
2508
2785
|
savedFormsCount: persistence.savedForms.length,
|
|
2786
|
+
clipboardReady: clipboard.hasClipboard,
|
|
2787
|
+
templateCopied,
|
|
2509
2788
|
onTitleChange: doc.updateTitle,
|
|
2510
2789
|
onLanguageChange: setLanguage,
|
|
2511
2790
|
onModeChange: setMode,
|
|
2512
2791
|
onNewForm: persistence.newForm,
|
|
2513
2792
|
onOpenLibrary: () => setShowLibrary(true),
|
|
2514
2793
|
onSaveExisting: persistence.saveExisting,
|
|
2515
|
-
onOpenJson: () => setShowJson(true)
|
|
2794
|
+
onOpenJson: () => setShowJson(true),
|
|
2795
|
+
onCopyTemplate: copyTemplate,
|
|
2796
|
+
onPasteTemplate: pasteTemplate
|
|
2516
2797
|
}
|
|
2517
2798
|
),
|
|
2518
|
-
persistence.loadingDraft ? /* @__PURE__ */ (0,
|
|
2519
|
-
/* @__PURE__ */ (0,
|
|
2520
|
-
/* @__PURE__ */ (0,
|
|
2521
|
-
] }) : mode === "build" ? /* @__PURE__ */ (0,
|
|
2522
|
-
/* @__PURE__ */ (0,
|
|
2523
|
-
/* @__PURE__ */ (0,
|
|
2799
|
+
persistence.loadingDraft ? /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { style: styles.loadingScreen, children: [
|
|
2800
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react19.Loader2, { size: 20, className: "spin" }),
|
|
2801
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { style: { marginTop: 8, fontSize: 13, color: "var(--fb-muted)" }, children: chrome.loadingDraft })
|
|
2802
|
+
] }) : mode === "build" ? /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "fb-work-area", style: styles.workArea, "data-mobile-panel": mobilePanel, children: [
|
|
2803
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "fb-mobile-bar", children: [
|
|
2804
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
|
|
2524
2805
|
"button",
|
|
2525
2806
|
{
|
|
2526
2807
|
type: "button",
|
|
@@ -2528,13 +2809,13 @@ var FormBuilder = (0, import_react8.forwardRef)(function FormBuilder2({
|
|
|
2528
2809
|
"aria-pressed": mobilePanel === "palette",
|
|
2529
2810
|
onClick: () => setMobilePanel((p) => p === "palette" ? "none" : "palette"),
|
|
2530
2811
|
children: [
|
|
2531
|
-
/* @__PURE__ */ (0,
|
|
2812
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react19.Menu, { size: 14 }),
|
|
2532
2813
|
" ",
|
|
2533
2814
|
chrome.paletteTabBlocks
|
|
2534
2815
|
]
|
|
2535
2816
|
}
|
|
2536
2817
|
),
|
|
2537
|
-
/* @__PURE__ */ (0,
|
|
2818
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
|
|
2538
2819
|
"button",
|
|
2539
2820
|
{
|
|
2540
2821
|
type: "button",
|
|
@@ -2542,16 +2823,16 @@ var FormBuilder = (0, import_react8.forwardRef)(function FormBuilder2({
|
|
|
2542
2823
|
"aria-pressed": mobilePanel === "inspector",
|
|
2543
2824
|
onClick: () => setMobilePanel((p) => p === "inspector" ? "none" : "inspector"),
|
|
2544
2825
|
children: [
|
|
2545
|
-
/* @__PURE__ */ (0,
|
|
2826
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react19.SlidersHorizontal, { size: 14 }),
|
|
2546
2827
|
" ",
|
|
2547
2828
|
chrome.properties
|
|
2548
2829
|
]
|
|
2549
2830
|
}
|
|
2550
2831
|
)
|
|
2551
2832
|
] }),
|
|
2552
|
-
/* @__PURE__ */ (0,
|
|
2553
|
-
mobilePanel !== "none" && /* @__PURE__ */ (0,
|
|
2554
|
-
/* @__PURE__ */ (0,
|
|
2833
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "fb-canvas-area", style: styles.workArea, children: [
|
|
2834
|
+
mobilePanel !== "none" && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "fb-mobile-backdrop", onClick: () => setMobilePanel("none") }),
|
|
2835
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
2555
2836
|
Palette,
|
|
2556
2837
|
{
|
|
2557
2838
|
activeSectionLabel,
|
|
@@ -2568,7 +2849,7 @@ var FormBuilder = (0, import_react8.forwardRef)(function FormBuilder2({
|
|
|
2568
2849
|
resetTheme
|
|
2569
2850
|
}
|
|
2570
2851
|
),
|
|
2571
|
-
/* @__PURE__ */ (0,
|
|
2852
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
2572
2853
|
Canvas,
|
|
2573
2854
|
{
|
|
2574
2855
|
sections: doc.sections,
|
|
@@ -2600,7 +2881,7 @@ var FormBuilder = (0, import_react8.forwardRef)(function FormBuilder2({
|
|
|
2600
2881
|
getDragHandleProps: drag.getDragHandleProps
|
|
2601
2882
|
}
|
|
2602
2883
|
),
|
|
2603
|
-
/* @__PURE__ */ (0,
|
|
2884
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
2604
2885
|
Inspector,
|
|
2605
2886
|
{
|
|
2606
2887
|
selected: doc.selected,
|
|
@@ -2615,7 +2896,7 @@ var FormBuilder = (0, import_react8.forwardRef)(function FormBuilder2({
|
|
|
2615
2896
|
}
|
|
2616
2897
|
)
|
|
2617
2898
|
] })
|
|
2618
|
-
] }) : /* @__PURE__ */ (0,
|
|
2899
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
2619
2900
|
PreviewPane,
|
|
2620
2901
|
{
|
|
2621
2902
|
title: doc.title,
|
|
@@ -2629,29 +2910,55 @@ var FormBuilder = (0, import_react8.forwardRef)(function FormBuilder2({
|
|
|
2629
2910
|
onSubmit
|
|
2630
2911
|
}
|
|
2631
2912
|
),
|
|
2632
|
-
showJson && /* @__PURE__ */ (0,
|
|
2633
|
-
showLibrary && /* @__PURE__ */ (0,
|
|
2913
|
+
showJson && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(JsonModal, { chrome, jsonString, copied, onCopy: copyJson, onClose: () => setShowJson(false) }),
|
|
2914
|
+
showLibrary && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
2634
2915
|
TemplatesModal,
|
|
2635
2916
|
{
|
|
2636
2917
|
chrome,
|
|
2637
2918
|
savedForms: persistence.savedForms,
|
|
2638
2919
|
currentFormId: persistence.currentFormId,
|
|
2920
|
+
manage: features.templates.manage,
|
|
2921
|
+
templateState: persistence.templateState,
|
|
2639
2922
|
onOpen: async (id) => {
|
|
2640
|
-
await persistence.loadForm(id);
|
|
2641
|
-
setShowLibrary(false);
|
|
2923
|
+
if (await persistence.loadForm(id)) setShowLibrary(false);
|
|
2642
2924
|
},
|
|
2643
2925
|
onDelete: persistence.deleteForm,
|
|
2644
2926
|
onClose: () => setShowLibrary(false)
|
|
2645
2927
|
}
|
|
2646
2928
|
),
|
|
2647
|
-
persistence.saveAsPrompt && /* @__PURE__ */ (0,
|
|
2929
|
+
persistence.saveAsPrompt && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
2648
2930
|
SaveAsModal,
|
|
2649
2931
|
{
|
|
2650
2932
|
chrome,
|
|
2651
2933
|
suggestedName: persistence.saveAsPrompt.suggestedName,
|
|
2934
|
+
templateState: persistence.templateState,
|
|
2652
2935
|
onSave: persistence.saveAs,
|
|
2653
2936
|
onClose: persistence.dismissSaveAsPrompt
|
|
2654
2937
|
}
|
|
2938
|
+
),
|
|
2939
|
+
pastePrompt && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
2940
|
+
ConfirmModal,
|
|
2941
|
+
{
|
|
2942
|
+
chrome,
|
|
2943
|
+
title: chrome.pasteTemplate,
|
|
2944
|
+
message: chrome.pasteTemplateConfirm,
|
|
2945
|
+
confirmLabel: chrome.confirmReplace,
|
|
2946
|
+
tone: "danger",
|
|
2947
|
+
onConfirm: () => {
|
|
2948
|
+
applyDocument(clipboard.readTemplate());
|
|
2949
|
+
setPastePrompt(false);
|
|
2950
|
+
},
|
|
2951
|
+
onClose: () => setPastePrompt(false)
|
|
2952
|
+
}
|
|
2953
|
+
),
|
|
2954
|
+
persistence.notice && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
2955
|
+
ConfirmModal,
|
|
2956
|
+
{
|
|
2957
|
+
chrome,
|
|
2958
|
+
title: persistence.notice.title,
|
|
2959
|
+
message: persistence.notice.message,
|
|
2960
|
+
onClose: persistence.dismissNotice
|
|
2961
|
+
}
|
|
2655
2962
|
)
|
|
2656
2963
|
] });
|
|
2657
2964
|
});
|
|
@@ -2673,9 +2980,17 @@ var DARK_THEME = {
|
|
|
2673
2980
|
};
|
|
2674
2981
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2675
2982
|
0 && (module.exports = {
|
|
2983
|
+
CLIPBOARD_KEY,
|
|
2676
2984
|
DARK_THEME,
|
|
2677
2985
|
DEFAULT_THEME,
|
|
2986
|
+
DOCUMENT_VERSION,
|
|
2987
|
+
DRAFT_KEY,
|
|
2678
2988
|
FormBuilder,
|
|
2679
|
-
|
|
2989
|
+
INDEX_KEY,
|
|
2990
|
+
formKey,
|
|
2991
|
+
localStorageAdapter,
|
|
2992
|
+
parseTemplate,
|
|
2993
|
+
savedFormId,
|
|
2994
|
+
serializeTemplate
|
|
2680
2995
|
});
|
|
2681
2996
|
//# sourceMappingURL=index.cjs.map
|