form-page-builder 2.2.0 → 2.4.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 +30 -1
- package/dist/index.cjs +93 -54
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -1
- package/dist/index.d.ts +26 -1
- package/dist/index.js +91 -54
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -119,6 +119,8 @@ declare const CHROME: {
|
|
|
119
119
|
loadingDraft: string;
|
|
120
120
|
templatesLimitReached: (max: number) => string;
|
|
121
121
|
templatesLimitTitle: string;
|
|
122
|
+
fieldCount: (n: number, max: number) => string;
|
|
123
|
+
fieldLimitReached: (max: number) => string;
|
|
122
124
|
confirm: string;
|
|
123
125
|
cancel: string;
|
|
124
126
|
dismiss: string;
|
|
@@ -272,6 +274,8 @@ declare const CHROME: {
|
|
|
272
274
|
loadingDraft: string;
|
|
273
275
|
templatesLimitReached: (max: number) => string;
|
|
274
276
|
templatesLimitTitle: string;
|
|
277
|
+
fieldCount: (n: number, max: number) => string;
|
|
278
|
+
fieldLimitReached: (max: number) => string;
|
|
275
279
|
confirm: string;
|
|
276
280
|
cancel: string;
|
|
277
281
|
dismiss: string;
|
|
@@ -619,6 +623,8 @@ interface TemplateChange {
|
|
|
619
623
|
interface FormBuilderFeatures {
|
|
620
624
|
/** Editable form title input in the Toolbar. Default true. */
|
|
621
625
|
naming?: boolean;
|
|
626
|
+
/** Render the form title as a heading above the fields in Preview. Default false — the host usually shows the form name in its own chrome. */
|
|
627
|
+
formTitle?: boolean;
|
|
622
628
|
/**
|
|
623
629
|
* Templates library. `true` (default) = full CRUD + toolbar "Save"; `false` = hidden.
|
|
624
630
|
* `{ manage: false }` = pick-and-apply only (no save/overwrite/delete). `{ max }` caps the count (default 5).
|
|
@@ -698,6 +704,25 @@ declare function serializeTemplate(document: FormDocument): string;
|
|
|
698
704
|
/** Accepts a `serializeTemplate` envelope or a bare document (e.g. "View JSON" output); `null` if not a document. */
|
|
699
705
|
declare function parseTemplate(raw: string | null | undefined): DocumentFields | null;
|
|
700
706
|
|
|
707
|
+
/** Current document schema version — stamped on everything persisted. */
|
|
708
|
+
declare const DOCUMENT_VERSION: 5;
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* Drops every field/section prop that equals its default, so persisted documents are smaller.
|
|
712
|
+
* Lossless: `migrateDocument` restores the defaults, so `migrateDocument(compactDocument(d))` equals `migrateDocument(d)`.
|
|
713
|
+
* Empty non-`en` translations are dropped too, and the result carries `compact: COMPACT_VERSION`.
|
|
714
|
+
* Extra top-level keys (`version`, `theme`, `themeOverrides`, …) pass through untouched.
|
|
715
|
+
*/
|
|
716
|
+
declare function compactDocument<T extends {
|
|
717
|
+
title?: unknown;
|
|
718
|
+
sections: {
|
|
719
|
+
title?: unknown;
|
|
720
|
+
fields: FormField[];
|
|
721
|
+
background?: string;
|
|
722
|
+
collapsed?: boolean;
|
|
723
|
+
}[];
|
|
724
|
+
}>(doc: T): T;
|
|
725
|
+
|
|
701
726
|
declare const DRAFT_KEY = "form-page-builder:draft";
|
|
702
727
|
declare const INDEX_KEY = "form-page-builder:index";
|
|
703
728
|
declare const formKey: (id: string) => string;
|
|
@@ -706,4 +731,4 @@ declare const savedFormId: (key: string) => string | null;
|
|
|
706
731
|
/** Copy/paste-template scratch slot — always localStorage, never the StorageAdapter. */
|
|
707
732
|
declare const CLIPBOARD_KEY = "form-page-builder:clipboard";
|
|
708
733
|
|
|
709
|
-
export { CLIPBOARD_KEY, type CheckboxField, type CheckboxGroupField, type ChromeShape, type ContentBlockType, DARK_THEME, DEFAULT_THEME, DRAFT_KEY, type DocumentFields, type FieldPatch, type FieldType, FormBuilder, type FormBuilderFeatures, type FormBuilderHandle, type FormBuilderProps, type FormDocument, type FormField, type FormTemplate, INDEX_KEY, type ImageField, type InputField, type InputFieldType, type LanguageOption, type LocalizedString, type Option, type ParagraphField, type RadioField, type SavedFormMeta, type Section, type SelectField, type StorageAdapter, type StringsShape, type SubmitPayload, type SubmitStyle, type TemplateChange, type TextareaField, type Theme, type ThemeLayout, type ThemeOverrides, type ToggleField, FormBuilder as default, formKey, localStorageAdapter, parseTemplate, savedFormId, serializeTemplate };
|
|
734
|
+
export { CLIPBOARD_KEY, type CheckboxField, type CheckboxGroupField, type ChromeShape, type ContentBlockType, DARK_THEME, DEFAULT_THEME, DOCUMENT_VERSION, DRAFT_KEY, type DocumentFields, type FieldPatch, type FieldType, FormBuilder, type FormBuilderFeatures, type FormBuilderHandle, type FormBuilderProps, type FormDocument, type FormField, type FormTemplate, INDEX_KEY, type ImageField, type InputField, type InputFieldType, type LanguageOption, type LocalizedString, type Option, type ParagraphField, type RadioField, type SavedFormMeta, type Section, type SelectField, type StorageAdapter, type StringsShape, type SubmitPayload, type SubmitStyle, type TemplateChange, type TextareaField, type Theme, type ThemeLayout, type ThemeOverrides, type ToggleField, compactDocument, FormBuilder as default, formKey, localStorageAdapter, parseTemplate, savedFormId, serializeTemplate };
|
package/dist/index.d.ts
CHANGED
|
@@ -119,6 +119,8 @@ declare const CHROME: {
|
|
|
119
119
|
loadingDraft: string;
|
|
120
120
|
templatesLimitReached: (max: number) => string;
|
|
121
121
|
templatesLimitTitle: string;
|
|
122
|
+
fieldCount: (n: number, max: number) => string;
|
|
123
|
+
fieldLimitReached: (max: number) => string;
|
|
122
124
|
confirm: string;
|
|
123
125
|
cancel: string;
|
|
124
126
|
dismiss: string;
|
|
@@ -272,6 +274,8 @@ declare const CHROME: {
|
|
|
272
274
|
loadingDraft: string;
|
|
273
275
|
templatesLimitReached: (max: number) => string;
|
|
274
276
|
templatesLimitTitle: string;
|
|
277
|
+
fieldCount: (n: number, max: number) => string;
|
|
278
|
+
fieldLimitReached: (max: number) => string;
|
|
275
279
|
confirm: string;
|
|
276
280
|
cancel: string;
|
|
277
281
|
dismiss: string;
|
|
@@ -619,6 +623,8 @@ interface TemplateChange {
|
|
|
619
623
|
interface FormBuilderFeatures {
|
|
620
624
|
/** Editable form title input in the Toolbar. Default true. */
|
|
621
625
|
naming?: boolean;
|
|
626
|
+
/** Render the form title as a heading above the fields in Preview. Default false — the host usually shows the form name in its own chrome. */
|
|
627
|
+
formTitle?: boolean;
|
|
622
628
|
/**
|
|
623
629
|
* Templates library. `true` (default) = full CRUD + toolbar "Save"; `false` = hidden.
|
|
624
630
|
* `{ manage: false }` = pick-and-apply only (no save/overwrite/delete). `{ max }` caps the count (default 5).
|
|
@@ -698,6 +704,25 @@ declare function serializeTemplate(document: FormDocument): string;
|
|
|
698
704
|
/** Accepts a `serializeTemplate` envelope or a bare document (e.g. "View JSON" output); `null` if not a document. */
|
|
699
705
|
declare function parseTemplate(raw: string | null | undefined): DocumentFields | null;
|
|
700
706
|
|
|
707
|
+
/** Current document schema version — stamped on everything persisted. */
|
|
708
|
+
declare const DOCUMENT_VERSION: 5;
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* Drops every field/section prop that equals its default, so persisted documents are smaller.
|
|
712
|
+
* Lossless: `migrateDocument` restores the defaults, so `migrateDocument(compactDocument(d))` equals `migrateDocument(d)`.
|
|
713
|
+
* Empty non-`en` translations are dropped too, and the result carries `compact: COMPACT_VERSION`.
|
|
714
|
+
* Extra top-level keys (`version`, `theme`, `themeOverrides`, …) pass through untouched.
|
|
715
|
+
*/
|
|
716
|
+
declare function compactDocument<T extends {
|
|
717
|
+
title?: unknown;
|
|
718
|
+
sections: {
|
|
719
|
+
title?: unknown;
|
|
720
|
+
fields: FormField[];
|
|
721
|
+
background?: string;
|
|
722
|
+
collapsed?: boolean;
|
|
723
|
+
}[];
|
|
724
|
+
}>(doc: T): T;
|
|
725
|
+
|
|
701
726
|
declare const DRAFT_KEY = "form-page-builder:draft";
|
|
702
727
|
declare const INDEX_KEY = "form-page-builder:index";
|
|
703
728
|
declare const formKey: (id: string) => string;
|
|
@@ -706,4 +731,4 @@ declare const savedFormId: (key: string) => string | null;
|
|
|
706
731
|
/** Copy/paste-template scratch slot — always localStorage, never the StorageAdapter. */
|
|
707
732
|
declare const CLIPBOARD_KEY = "form-page-builder:clipboard";
|
|
708
733
|
|
|
709
|
-
export { CLIPBOARD_KEY, type CheckboxField, type CheckboxGroupField, type ChromeShape, type ContentBlockType, DARK_THEME, DEFAULT_THEME, DRAFT_KEY, type DocumentFields, type FieldPatch, type FieldType, FormBuilder, type FormBuilderFeatures, type FormBuilderHandle, type FormBuilderProps, type FormDocument, type FormField, type FormTemplate, INDEX_KEY, type ImageField, type InputField, type InputFieldType, type LanguageOption, type LocalizedString, type Option, type ParagraphField, type RadioField, type SavedFormMeta, type Section, type SelectField, type StorageAdapter, type StringsShape, type SubmitPayload, type SubmitStyle, type TemplateChange, type TextareaField, type Theme, type ThemeLayout, type ThemeOverrides, type ToggleField, FormBuilder as default, formKey, localStorageAdapter, parseTemplate, savedFormId, serializeTemplate };
|
|
734
|
+
export { CLIPBOARD_KEY, type CheckboxField, type CheckboxGroupField, type ChromeShape, type ContentBlockType, DARK_THEME, DEFAULT_THEME, DOCUMENT_VERSION, DRAFT_KEY, type DocumentFields, type FieldPatch, type FieldType, FormBuilder, type FormBuilderFeatures, type FormBuilderHandle, type FormBuilderProps, type FormDocument, type FormField, type FormTemplate, INDEX_KEY, type ImageField, type InputField, type InputFieldType, type LanguageOption, type LocalizedString, type Option, type ParagraphField, type RadioField, type SavedFormMeta, type Section, type SelectField, type StorageAdapter, type StringsShape, type SubmitPayload, type SubmitStyle, type TemplateChange, type TextareaField, type Theme, type ThemeLayout, type ThemeOverrides, type ToggleField, compactDocument, FormBuilder as default, formKey, localStorageAdapter, parseTemplate, savedFormId, serializeTemplate };
|
package/dist/index.js
CHANGED
|
@@ -196,6 +196,8 @@ var CHROME = {
|
|
|
196
196
|
loadingDraft: "Loading your draft...",
|
|
197
197
|
templatesLimitReached: (max) => `You can save up to ${max} template${max === 1 ? "" : "s"}. Delete one to save another.`,
|
|
198
198
|
templatesLimitTitle: "Template limit reached",
|
|
199
|
+
fieldCount: (n, max) => `Fields: ${n} / ${max}`,
|
|
200
|
+
fieldLimitReached: (max) => `Field limit reached (${max}). Delete a field to add another.`,
|
|
199
201
|
confirm: "Confirm",
|
|
200
202
|
cancel: "Cancel",
|
|
201
203
|
dismiss: "OK",
|
|
@@ -349,6 +351,8 @@ var CHROME = {
|
|
|
349
351
|
loadingDraft: "\u4E0B\u66F8\u304D\u3092\u8AAD\u307F\u8FBC\u3093\u3067\u3044\u307E\u3059...",
|
|
350
352
|
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`,
|
|
351
353
|
templatesLimitTitle: "\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u306E\u4E0A\u9650\u306B\u9054\u3057\u307E\u3057\u305F",
|
|
354
|
+
fieldCount: (n, max) => `\u9805\u76EE\u6570: ${n} / ${max}`,
|
|
355
|
+
fieldLimitReached: (max) => `\u9805\u76EE\u6570\u306E\u4E0A\u9650\uFF08${max}\u4EF6\uFF09\u306B\u9054\u3057\u307E\u3057\u305F\u3002\u8FFD\u52A0\u3059\u308B\u306B\u306F\u9805\u76EE\u3092\u524A\u9664\u3057\u3066\u304F\u3060\u3055\u3044\u3002`,
|
|
352
356
|
confirm: "\u78BA\u5B9A",
|
|
353
357
|
cancel: "\u30AD\u30E3\u30F3\u30BB\u30EB",
|
|
354
358
|
dismiss: "OK",
|
|
@@ -558,12 +562,8 @@ function defaultSection(titleEn = "") {
|
|
|
558
562
|
return { id: genSectionId(), title: bi(titleEn, ""), background: "", collapsed: false, fields: [] };
|
|
559
563
|
}
|
|
560
564
|
|
|
561
|
-
// src/lib/submittable.ts
|
|
562
|
-
function sectionHasFormFields(section) {
|
|
563
|
-
return section.fields.some((f) => !getMeta(f.type).isContent);
|
|
564
|
-
}
|
|
565
|
-
|
|
566
565
|
// src/lib/migrate.ts
|
|
566
|
+
var DOCUMENT_VERSION = 5;
|
|
567
567
|
function migrateField(field) {
|
|
568
568
|
let f = { ...field };
|
|
569
569
|
if (LEGACY_INPUT_TYPES.includes(f.type)) f = { ...f, type: "input", inputType: f.type };
|
|
@@ -589,30 +589,21 @@ function migrateField(field) {
|
|
|
589
589
|
f.submitScope = f.submitScope === "section" ? "section" : "form";
|
|
590
590
|
}
|
|
591
591
|
f.verticalAlign = f.verticalAlign || "top";
|
|
592
|
+
const meta = getMeta(f.type);
|
|
593
|
+
f.width = f.width || "1/1";
|
|
594
|
+
f.labelPosition = f.labelPosition || "top";
|
|
595
|
+
f.showIcon = !!f.showIcon;
|
|
596
|
+
f.displayIcon = f.displayIcon || meta.defaultIcon;
|
|
597
|
+
if (!meta.isContent) {
|
|
598
|
+
f.required = !!f.required;
|
|
599
|
+
if (meta.placeholder && !f.placeholder) f.placeholder = bi();
|
|
600
|
+
if (f.defaultValue === void 0) f.defaultValue = meta.boolean ? false : meta.multiValue ? [] : "";
|
|
601
|
+
}
|
|
592
602
|
return f;
|
|
593
603
|
}
|
|
594
604
|
function migrateFields(fields) {
|
|
595
605
|
return (fields || []).map(migrateField);
|
|
596
606
|
}
|
|
597
|
-
function synthesizeButtonField(label, style, scope) {
|
|
598
|
-
const resolvedLabel = typeof label === "string" && label ? bi(label) : label && typeof label === "object" && (label.en || label.ja) ? label : bi("Submit", "");
|
|
599
|
-
return {
|
|
600
|
-
id: nextId(),
|
|
601
|
-
type: "button",
|
|
602
|
-
label: resolvedLabel,
|
|
603
|
-
hideLabel: false,
|
|
604
|
-
width: "1/1",
|
|
605
|
-
verticalAlign: "top",
|
|
606
|
-
labelPosition: "top",
|
|
607
|
-
showIcon: false,
|
|
608
|
-
displayIcon: "Type",
|
|
609
|
-
action: "submit",
|
|
610
|
-
submitScope: scope,
|
|
611
|
-
buttonStyle: style || { color: "", size: "md" },
|
|
612
|
-
href: "",
|
|
613
|
-
target: "_self"
|
|
614
|
-
};
|
|
615
|
-
}
|
|
616
607
|
function migrateDocument(raw) {
|
|
617
608
|
if (!raw) return null;
|
|
618
609
|
const title = typeof raw.title === "string" ? bi(raw.title) : raw.title || bi();
|
|
@@ -629,25 +620,6 @@ function migrateDocument(raw) {
|
|
|
629
620
|
} else {
|
|
630
621
|
sections = [{ ...defaultSection(), fields: migrateFields(raw.fields || []) }];
|
|
631
622
|
}
|
|
632
|
-
const legacy = raw.version === void 0 || raw.version < 5;
|
|
633
|
-
if (legacy) {
|
|
634
|
-
resyncIdCounter(sections.flatMap((s) => s.fields));
|
|
635
|
-
const submitMode = raw.submitMode === "perSection" ? "perSection" : "combined";
|
|
636
|
-
const submitLabel = raw.submitLabel;
|
|
637
|
-
const submitStyle = raw.submitStyle || { color: "", size: "md" };
|
|
638
|
-
if (submitMode === "perSection" && raw.sections) {
|
|
639
|
-
sections = sections.map((s, i) => {
|
|
640
|
-
if (!sectionHasFormFields(s)) return s;
|
|
641
|
-
const rawSection = raw.sections[i] || {};
|
|
642
|
-
const label = rawSection.submitLabel || submitLabel;
|
|
643
|
-
const style = rawSection.submitStyle || submitStyle;
|
|
644
|
-
return { ...s, fields: [...s.fields, synthesizeButtonField(label, style, "section")] };
|
|
645
|
-
});
|
|
646
|
-
} else if (sections.some(sectionHasFormFields)) {
|
|
647
|
-
const lastIdx = sections.length - 1;
|
|
648
|
-
sections = sections.map((s, i) => i === lastIdx ? { ...s, fields: [...s.fields, synthesizeButtonField(submitLabel, submitStyle, "form")] } : s);
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
623
|
return { title, themeOverrides, sections };
|
|
652
624
|
}
|
|
653
625
|
|
|
@@ -675,6 +647,7 @@ function parseTemplate(raw) {
|
|
|
675
647
|
var DEFAULT_MAX_TEMPLATES = 5;
|
|
676
648
|
var DEFAULT_FEATURES = {
|
|
677
649
|
naming: true,
|
|
650
|
+
formTitle: false,
|
|
678
651
|
templates: { enabled: true, manage: true, max: DEFAULT_MAX_TEMPLATES },
|
|
679
652
|
templateClipboard: true,
|
|
680
653
|
newForm: true,
|
|
@@ -878,7 +851,9 @@ function useFormDocument({ language, chrome }) {
|
|
|
878
851
|
...s,
|
|
879
852
|
fields: s.fields.map((f) => {
|
|
880
853
|
if (f.id !== fieldId || !("options" in f) || !f.options) return f;
|
|
881
|
-
const
|
|
854
|
+
const used = new Set(f.options.map((o) => o.value));
|
|
855
|
+
let n = f.options.length + 1;
|
|
856
|
+
while (used.has(`option_${n}`)) n++;
|
|
882
857
|
return { ...f, options: [...f.options, { label: withLang(bi(), language, chrome.optionSeed(n)), value: `option_${n}` }] };
|
|
883
858
|
})
|
|
884
859
|
})));
|
|
@@ -886,7 +861,14 @@ function useFormDocument({ language, chrome }) {
|
|
|
886
861
|
function removeOption(fieldId, optIdx) {
|
|
887
862
|
setSections((prev) => prev.map((s) => ({
|
|
888
863
|
...s,
|
|
889
|
-
fields: s.fields.map((f) =>
|
|
864
|
+
fields: s.fields.map((f) => {
|
|
865
|
+
if (f.id !== fieldId || !("options" in f) || !f.options) return f;
|
|
866
|
+
const removed = f.options[optIdx]?.value;
|
|
867
|
+
const options = f.options.filter((_, i) => i !== optIdx);
|
|
868
|
+
if (f.type === "checkboxGroup") return { ...f, options, defaultValue: f.defaultValue.filter((v) => v !== removed) };
|
|
869
|
+
if ((f.type === "select" || f.type === "radio") && f.defaultValue === removed) return { ...f, options, defaultValue: "" };
|
|
870
|
+
return { ...f, options };
|
|
871
|
+
})
|
|
890
872
|
})));
|
|
891
873
|
}
|
|
892
874
|
function loadDocument(doc) {
|
|
@@ -946,6 +928,51 @@ var formKey = (id) => `form-page-builder:saved:${id}`;
|
|
|
946
928
|
var savedFormId = (key) => key.startsWith("form-page-builder:saved:") ? key.slice("form-page-builder:saved:".length) : null;
|
|
947
929
|
var CLIPBOARD_KEY = "form-page-builder:clipboard";
|
|
948
930
|
|
|
931
|
+
// src/lib/compact.ts
|
|
932
|
+
var BASE_DEFAULTS = {
|
|
933
|
+
hideLabel: false,
|
|
934
|
+
width: "1/1",
|
|
935
|
+
verticalAlign: "top",
|
|
936
|
+
labelPosition: "top",
|
|
937
|
+
showIcon: false,
|
|
938
|
+
required: false
|
|
939
|
+
};
|
|
940
|
+
var COMPACT_VERSION = 1;
|
|
941
|
+
var slimLocalized = (v) => v && typeof v === "object" ? Object.fromEntries(Object.entries(v).filter(([lang, s]) => lang === "en" || s !== "")) : v;
|
|
942
|
+
var isEmptyLocalized = (v) => !!v && typeof v === "object" && Object.values(v).every((s) => !s);
|
|
943
|
+
var isEmptyDefault = (v) => v === "" || v === false || Array.isArray(v) && v.length === 0;
|
|
944
|
+
function compactField(field) {
|
|
945
|
+
const out = {};
|
|
946
|
+
const meta = getMeta(field.type);
|
|
947
|
+
for (const [key, value] of Object.entries(field)) {
|
|
948
|
+
if (key in BASE_DEFAULTS && value === BASE_DEFAULTS[key]) continue;
|
|
949
|
+
if (key === "displayIcon" && value === meta.defaultIcon) continue;
|
|
950
|
+
if (key === "placeholder" && isEmptyLocalized(value)) continue;
|
|
951
|
+
if (key === "defaultValue" && !meta.isContent && isEmptyDefault(value)) continue;
|
|
952
|
+
if (key === "label" || key === "placeholder" || key === "content" || key === "alt") out[key] = slimLocalized(value);
|
|
953
|
+
else if (key === "options" && Array.isArray(value)) out[key] = value.map((o) => ({ ...o, label: slimLocalized(o.label) }));
|
|
954
|
+
else out[key] = value;
|
|
955
|
+
}
|
|
956
|
+
return out;
|
|
957
|
+
}
|
|
958
|
+
function compactDocument(doc) {
|
|
959
|
+
return {
|
|
960
|
+
...doc,
|
|
961
|
+
compact: COMPACT_VERSION,
|
|
962
|
+
...doc.title !== void 0 ? { title: slimLocalized(doc.title) } : {},
|
|
963
|
+
sections: doc.sections.map((s) => {
|
|
964
|
+
const { background, collapsed, fields, ...rest } = s;
|
|
965
|
+
return {
|
|
966
|
+
...rest,
|
|
967
|
+
...s.title !== void 0 ? { title: slimLocalized(s.title) } : {},
|
|
968
|
+
...background ? { background } : {},
|
|
969
|
+
...collapsed ? { collapsed } : {},
|
|
970
|
+
fields: fields.map(compactField)
|
|
971
|
+
};
|
|
972
|
+
})
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
|
|
949
976
|
// src/hooks/usePersistence.ts
|
|
950
977
|
function snapshotOf(d) {
|
|
951
978
|
return JSON.stringify({ title: d.title, themeOverrides: d.themeOverrides, sections: d.sections });
|
|
@@ -1038,7 +1065,7 @@ function usePersistence({
|
|
|
1038
1065
|
if (autosaveTimer.current) clearTimeout(autosaveTimer.current);
|
|
1039
1066
|
autosaveTimer.current = setTimeout(async () => {
|
|
1040
1067
|
try {
|
|
1041
|
-
await storage.set(DRAFT_KEY, JSON.stringify({ ...document2, currentFormId }));
|
|
1068
|
+
await storage.set(DRAFT_KEY, JSON.stringify({ version: DOCUMENT_VERSION, ...compactDocument(document2), currentFormId }));
|
|
1042
1069
|
setSaveState("saved");
|
|
1043
1070
|
} catch {
|
|
1044
1071
|
setSaveState("error");
|
|
@@ -1061,7 +1088,7 @@ function usePersistence({
|
|
|
1061
1088
|
try {
|
|
1062
1089
|
const newTitle = bi(name, "");
|
|
1063
1090
|
const docToSave = { ...document2, title: newTitle };
|
|
1064
|
-
await storage.set(formKey(id), JSON.stringify({ ...docToSave, id, updatedAt: now }));
|
|
1091
|
+
await storage.set(formKey(id), JSON.stringify({ version: DOCUMENT_VERSION, ...compactDocument(docToSave), id, updatedAt: now }));
|
|
1065
1092
|
const next = [...savedForms, { id, title: name, updatedAt: now }];
|
|
1066
1093
|
await storage.set(INDEX_KEY, JSON.stringify(next));
|
|
1067
1094
|
setSavedForms(next);
|
|
@@ -1084,7 +1111,7 @@ function usePersistence({
|
|
|
1084
1111
|
const now = Date.now();
|
|
1085
1112
|
setTemplateState("saving");
|
|
1086
1113
|
try {
|
|
1087
|
-
await storage.set(formKey(currentFormId), JSON.stringify({ ...document2, id: currentFormId, updatedAt: now }));
|
|
1114
|
+
await storage.set(formKey(currentFormId), JSON.stringify({ version: DOCUMENT_VERSION, ...compactDocument(document2), id: currentFormId, updatedAt: now }));
|
|
1088
1115
|
const next = savedForms.map((f) => f.id === currentFormId ? { ...f, title: t(document2.title, "en"), updatedAt: now } : f);
|
|
1089
1116
|
setSavedForms(next);
|
|
1090
1117
|
await storage.set(INDEX_KEY, JSON.stringify(next));
|
|
@@ -1100,11 +1127,13 @@ function usePersistence({
|
|
|
1100
1127
|
try {
|
|
1101
1128
|
const raw = await storage.get(formKey(id));
|
|
1102
1129
|
if (!raw) {
|
|
1130
|
+
console.error(`[form-page-builder] loadForm(${id}): storage returned no data for this id`);
|
|
1103
1131
|
setTemplateState("error");
|
|
1104
1132
|
return false;
|
|
1105
1133
|
}
|
|
1106
1134
|
const doc = migrateDocument(JSON.parse(raw));
|
|
1107
1135
|
if (!doc) {
|
|
1136
|
+
console.error(`[form-page-builder] loadForm(${id}): stored data failed migrateDocument()`, raw);
|
|
1108
1137
|
setTemplateState("error");
|
|
1109
1138
|
return false;
|
|
1110
1139
|
}
|
|
@@ -1116,7 +1145,8 @@ function usePersistence({
|
|
|
1116
1145
|
setTemplateState("idle");
|
|
1117
1146
|
onTemplateChange?.({ id, title: t(doc.title, language), source: "applied" });
|
|
1118
1147
|
return true;
|
|
1119
|
-
} catch {
|
|
1148
|
+
} catch (err) {
|
|
1149
|
+
console.error(`[form-page-builder] loadForm(${id}) threw`, err);
|
|
1120
1150
|
setTemplateState("error");
|
|
1121
1151
|
return false;
|
|
1122
1152
|
}
|
|
@@ -1124,6 +1154,7 @@ function usePersistence({
|
|
|
1124
1154
|
async function deleteForm(id) {
|
|
1125
1155
|
if (!templateManage) return;
|
|
1126
1156
|
const removed = savedForms.find((f) => f.id === id);
|
|
1157
|
+
setTemplateState("loading");
|
|
1127
1158
|
try {
|
|
1128
1159
|
await storage.delete(formKey(id));
|
|
1129
1160
|
const next = savedForms.filter((f) => f.id !== id);
|
|
@@ -1133,6 +1164,7 @@ function usePersistence({
|
|
|
1133
1164
|
setCurrentFormId(null);
|
|
1134
1165
|
savedSnapshot.current = null;
|
|
1135
1166
|
}
|
|
1167
|
+
setTemplateState("idle");
|
|
1136
1168
|
onTemplateChange?.({ id: currentFormId === id ? null : id, title: removed?.title ?? "", source: "deleted" });
|
|
1137
1169
|
} catch {
|
|
1138
1170
|
setTemplateState("error");
|
|
@@ -1640,6 +1672,7 @@ function Palette({
|
|
|
1640
1672
|
] }),
|
|
1641
1673
|
formTypes.length > 0 && /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
1642
1674
|
/* @__PURE__ */ jsx5("div", { style: { ...styles.panelHeading, marginTop: 16 }, children: chrome.formFields }),
|
|
1675
|
+
typeof features.maxFields === "number" && /* @__PURE__ */ jsx5("div", { role: "status", style: { ...styles.activeSectionHint, marginBottom: 8, ...atFieldCap ? { color: "var(--fb-danger)" } : {} }, children: atFieldCap ? chrome.fieldLimitReached(features.maxFields) : chrome.fieldCount(fieldCount, features.maxFields) }),
|
|
1643
1676
|
/* @__PURE__ */ jsx5("div", { style: styles.paletteList, children: formTypes.map((f) => {
|
|
1644
1677
|
const Icon = f.icon;
|
|
1645
1678
|
const itemStyle = atFieldCap ? { ...styles.paletteItem, opacity: 0.45, cursor: "not-allowed" } : styles.paletteItem;
|
|
@@ -1909,6 +1942,8 @@ function FieldTicket({
|
|
|
1909
1942
|
{
|
|
1910
1943
|
...dropZoneHandlers,
|
|
1911
1944
|
onClick: onSelect,
|
|
1945
|
+
className: `fb-field fb-field--${field.type} fb-field--${field.id}`,
|
|
1946
|
+
"data-field-id": field.id,
|
|
1912
1947
|
style: {
|
|
1913
1948
|
...styles.ticket,
|
|
1914
1949
|
flex: `1 1 calc(${widthPct} - 10px)`,
|
|
@@ -2438,16 +2473,16 @@ function PreviewPane({ title, sections, onFieldChange, language, strings, chrome
|
|
|
2438
2473
|
return /* @__PURE__ */ jsxs12("div", { style: styles.previewWrap, children: [
|
|
2439
2474
|
/* @__PURE__ */ jsxs12("div", { style: { width: "100%", maxWidth, display: "flex", flexDirection: "column", alignItems: "center" }, children: [
|
|
2440
2475
|
features.deviceToggle && /* @__PURE__ */ jsx14("div", { style: styles.previewToolbar, children: /* @__PURE__ */ jsx14(Segmented, { options: deviceOptions, value: device, onChange: setDevice }) }),
|
|
2441
|
-
/* @__PURE__ */ jsxs12("div", { style: { ...styles.previewCard, maxWidth, width: "100%" }, children: [
|
|
2442
|
-
/* @__PURE__ */ jsx14("h2", { style: styles.previewTitle, children: t(title, language) }),
|
|
2476
|
+
/* @__PURE__ */ jsxs12("div", { className: "fb-form", style: { ...styles.previewCard, maxWidth, width: "100%" }, children: [
|
|
2477
|
+
features.formTitle && /* @__PURE__ */ jsx14("h2", { style: styles.previewTitle, children: t(title, language) }),
|
|
2443
2478
|
allFields.length === 0 && /* @__PURE__ */ jsx14("p", { style: { color: "var(--fb-muted)", fontSize: 14 }, children: strings.addFieldsHint }),
|
|
2444
2479
|
sections.map((section) => {
|
|
2445
|
-
return /* @__PURE__ */ jsxs12("div", { style: { background: section.background || "transparent", padding: section.background ? 16 : 0, borderRadius: section.background ? 10 : 0, marginBottom: "var(--fb-space-section)" }, children: [
|
|
2480
|
+
return /* @__PURE__ */ jsxs12("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: [
|
|
2446
2481
|
t(section.title, language) && /* @__PURE__ */ jsx14("h3", { style: styles.sectionRuntimeTitle, children: t(section.title, language) }),
|
|
2447
2482
|
/* @__PURE__ */ jsx14("div", { style: styles.previewGrid, children: section.fields.map((field) => {
|
|
2448
2483
|
const width = effectiveWidth(field.width, device);
|
|
2449
2484
|
const flexBasis = `1 1 calc(${WIDTH_PERCENT[width]} - 14px)`;
|
|
2450
|
-
return /* @__PURE__ */ jsxs12("div", { style: { flex: flexBasis, minWidth: 0, alignSelf: ALIGN_MAP[field.verticalAlign || "top"] }, children: [
|
|
2485
|
+
return /* @__PURE__ */ jsxs12("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: [
|
|
2451
2486
|
/* @__PURE__ */ jsx14(FieldBlock, { field, lang: language, onFieldChange, strings, chrome, error: errors[field.id], onButtonAction: handleButtonAction }),
|
|
2452
2487
|
field.type === "button" && field.id === attemptedButtonId && Object.keys(errors).length > 0 && /* @__PURE__ */ jsxs12("p", { style: styles.formErrorNote, children: [
|
|
2453
2488
|
/* @__PURE__ */ jsx14(CircleAlert2, { size: 13 }),
|
|
@@ -2742,7 +2777,7 @@ var FormBuilder = forwardRef(function FormBuilder2({
|
|
|
2742
2777
|
ensureActiveSection: () => doc.setActiveSectionId((prev) => prev ?? doc.sections[0]?.id ?? null)
|
|
2743
2778
|
});
|
|
2744
2779
|
const jsonDoc = useMemo2(() => ({
|
|
2745
|
-
version:
|
|
2780
|
+
version: DOCUMENT_VERSION,
|
|
2746
2781
|
title: doc.title,
|
|
2747
2782
|
theme,
|
|
2748
2783
|
themeOverrides,
|
|
@@ -3019,9 +3054,11 @@ export {
|
|
|
3019
3054
|
CLIPBOARD_KEY,
|
|
3020
3055
|
DARK_THEME,
|
|
3021
3056
|
DEFAULT_THEME,
|
|
3057
|
+
DOCUMENT_VERSION,
|
|
3022
3058
|
DRAFT_KEY,
|
|
3023
3059
|
FormBuilder_default as FormBuilder,
|
|
3024
3060
|
INDEX_KEY,
|
|
3061
|
+
compactDocument,
|
|
3025
3062
|
FormBuilder_default as default,
|
|
3026
3063
|
formKey,
|
|
3027
3064
|
localStorageAdapter,
|