form-page-builder 2.3.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/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;
@@ -703,6 +707,22 @@ declare function parseTemplate(raw: string | null | undefined): DocumentFields |
703
707
  /** Current document schema version — stamped on everything persisted. */
704
708
  declare const DOCUMENT_VERSION: 5;
705
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
+
706
726
  declare const DRAFT_KEY = "form-page-builder:draft";
707
727
  declare const INDEX_KEY = "form-page-builder:index";
708
728
  declare const formKey: (id: string) => string;
@@ -711,4 +731,4 @@ declare const savedFormId: (key: string) => string | null;
711
731
  /** Copy/paste-template scratch slot — always localStorage, never the StorageAdapter. */
712
732
  declare const CLIPBOARD_KEY = "form-page-builder:clipboard";
713
733
 
714
- 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, 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;
@@ -703,6 +707,22 @@ declare function parseTemplate(raw: string | null | undefined): DocumentFields |
703
707
  /** Current document schema version — stamped on everything persisted. */
704
708
  declare const DOCUMENT_VERSION: 5;
705
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
+
706
726
  declare const DRAFT_KEY = "form-page-builder:draft";
707
727
  declare const INDEX_KEY = "form-page-builder:index";
708
728
  declare const formKey: (id: string) => string;
@@ -711,4 +731,4 @@ declare const savedFormId: (key: string) => string | null;
711
731
  /** Copy/paste-template scratch slot — always localStorage, never the StorageAdapter. */
712
732
  declare const CLIPBOARD_KEY = "form-page-builder:clipboard";
713
733
 
714
- 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, 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",
@@ -585,6 +589,16 @@ function migrateField(field) {
585
589
  f.submitScope = f.submitScope === "section" ? "section" : "form";
586
590
  }
587
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
+ }
588
602
  return f;
589
603
  }
590
604
  function migrateFields(fields) {
@@ -837,7 +851,9 @@ function useFormDocument({ language, chrome }) {
837
851
  ...s,
838
852
  fields: s.fields.map((f) => {
839
853
  if (f.id !== fieldId || !("options" in f) || !f.options) return f;
840
- const n = f.options.length + 1;
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++;
841
857
  return { ...f, options: [...f.options, { label: withLang(bi(), language, chrome.optionSeed(n)), value: `option_${n}` }] };
842
858
  })
843
859
  })));
@@ -845,7 +861,14 @@ function useFormDocument({ language, chrome }) {
845
861
  function removeOption(fieldId, optIdx) {
846
862
  setSections((prev) => prev.map((s) => ({
847
863
  ...s,
848
- fields: s.fields.map((f) => f.id !== fieldId || !("options" in f) || !f.options ? f : { ...f, options: f.options.filter((_, i) => i !== optIdx) })
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
+ })
849
872
  })));
850
873
  }
851
874
  function loadDocument(doc) {
@@ -905,6 +928,51 @@ var formKey = (id) => `form-page-builder:saved:${id}`;
905
928
  var savedFormId = (key) => key.startsWith("form-page-builder:saved:") ? key.slice("form-page-builder:saved:".length) : null;
906
929
  var CLIPBOARD_KEY = "form-page-builder:clipboard";
907
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
+
908
976
  // src/hooks/usePersistence.ts
909
977
  function snapshotOf(d) {
910
978
  return JSON.stringify({ title: d.title, themeOverrides: d.themeOverrides, sections: d.sections });
@@ -997,7 +1065,7 @@ function usePersistence({
997
1065
  if (autosaveTimer.current) clearTimeout(autosaveTimer.current);
998
1066
  autosaveTimer.current = setTimeout(async () => {
999
1067
  try {
1000
- await storage.set(DRAFT_KEY, JSON.stringify({ version: DOCUMENT_VERSION, ...document2, currentFormId }));
1068
+ await storage.set(DRAFT_KEY, JSON.stringify({ version: DOCUMENT_VERSION, ...compactDocument(document2), currentFormId }));
1001
1069
  setSaveState("saved");
1002
1070
  } catch {
1003
1071
  setSaveState("error");
@@ -1020,7 +1088,7 @@ function usePersistence({
1020
1088
  try {
1021
1089
  const newTitle = bi(name, "");
1022
1090
  const docToSave = { ...document2, title: newTitle };
1023
- await storage.set(formKey(id), JSON.stringify({ version: DOCUMENT_VERSION, ...docToSave, id, updatedAt: now }));
1091
+ await storage.set(formKey(id), JSON.stringify({ version: DOCUMENT_VERSION, ...compactDocument(docToSave), id, updatedAt: now }));
1024
1092
  const next = [...savedForms, { id, title: name, updatedAt: now }];
1025
1093
  await storage.set(INDEX_KEY, JSON.stringify(next));
1026
1094
  setSavedForms(next);
@@ -1043,7 +1111,7 @@ function usePersistence({
1043
1111
  const now = Date.now();
1044
1112
  setTemplateState("saving");
1045
1113
  try {
1046
- await storage.set(formKey(currentFormId), JSON.stringify({ version: DOCUMENT_VERSION, ...document2, id: currentFormId, updatedAt: now }));
1114
+ await storage.set(formKey(currentFormId), JSON.stringify({ version: DOCUMENT_VERSION, ...compactDocument(document2), id: currentFormId, updatedAt: now }));
1047
1115
  const next = savedForms.map((f) => f.id === currentFormId ? { ...f, title: t(document2.title, "en"), updatedAt: now } : f);
1048
1116
  setSavedForms(next);
1049
1117
  await storage.set(INDEX_KEY, JSON.stringify(next));
@@ -1059,11 +1127,13 @@ function usePersistence({
1059
1127
  try {
1060
1128
  const raw = await storage.get(formKey(id));
1061
1129
  if (!raw) {
1130
+ console.error(`[form-page-builder] loadForm(${id}): storage returned no data for this id`);
1062
1131
  setTemplateState("error");
1063
1132
  return false;
1064
1133
  }
1065
1134
  const doc = migrateDocument(JSON.parse(raw));
1066
1135
  if (!doc) {
1136
+ console.error(`[form-page-builder] loadForm(${id}): stored data failed migrateDocument()`, raw);
1067
1137
  setTemplateState("error");
1068
1138
  return false;
1069
1139
  }
@@ -1075,7 +1145,8 @@ function usePersistence({
1075
1145
  setTemplateState("idle");
1076
1146
  onTemplateChange?.({ id, title: t(doc.title, language), source: "applied" });
1077
1147
  return true;
1078
- } catch {
1148
+ } catch (err) {
1149
+ console.error(`[form-page-builder] loadForm(${id}) threw`, err);
1079
1150
  setTemplateState("error");
1080
1151
  return false;
1081
1152
  }
@@ -1083,6 +1154,7 @@ function usePersistence({
1083
1154
  async function deleteForm(id) {
1084
1155
  if (!templateManage) return;
1085
1156
  const removed = savedForms.find((f) => f.id === id);
1157
+ setTemplateState("loading");
1086
1158
  try {
1087
1159
  await storage.delete(formKey(id));
1088
1160
  const next = savedForms.filter((f) => f.id !== id);
@@ -1092,6 +1164,7 @@ function usePersistence({
1092
1164
  setCurrentFormId(null);
1093
1165
  savedSnapshot.current = null;
1094
1166
  }
1167
+ setTemplateState("idle");
1095
1168
  onTemplateChange?.({ id: currentFormId === id ? null : id, title: removed?.title ?? "", source: "deleted" });
1096
1169
  } catch {
1097
1170
  setTemplateState("error");
@@ -1599,6 +1672,7 @@ function Palette({
1599
1672
  ] }),
1600
1673
  formTypes.length > 0 && /* @__PURE__ */ jsxs5(Fragment2, { children: [
1601
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) }),
1602
1676
  /* @__PURE__ */ jsx5("div", { style: styles.paletteList, children: formTypes.map((f) => {
1603
1677
  const Icon = f.icon;
1604
1678
  const itemStyle = atFieldCap ? { ...styles.paletteItem, opacity: 0.45, cursor: "not-allowed" } : styles.paletteItem;
@@ -2984,6 +3058,7 @@ export {
2984
3058
  DRAFT_KEY,
2985
3059
  FormBuilder_default as FormBuilder,
2986
3060
  INDEX_KEY,
3061
+ compactDocument,
2987
3062
  FormBuilder_default as default,
2988
3063
  formKey,
2989
3064
  localStorageAdapter,