form-page-builder 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/index.cjs +73 -57
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +85 -69
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -592,6 +592,10 @@ interface FormBuilderFeatures {
|
|
|
592
592
|
sections?: boolean;
|
|
593
593
|
/** Drag-to-reorder fields within a section. Default true. */
|
|
594
594
|
dragReorder?: boolean;
|
|
595
|
+
/** Laptop/Tablet/Mobile width switcher above the Preview canvas. Default true. */
|
|
596
|
+
deviceToggle?: boolean;
|
|
597
|
+
/** Caps the number of input-type fields (not content blocks) addable across the whole document. Default undefined (unlimited). */
|
|
598
|
+
maxFields?: number;
|
|
595
599
|
}
|
|
596
600
|
interface FormBuilderProps {
|
|
597
601
|
theme?: Partial<Theme>;
|
|
@@ -605,6 +609,10 @@ interface FormBuilderProps {
|
|
|
605
609
|
onSubmit?: (payload: SubmitPayload) => void;
|
|
606
610
|
/** Seeds the builder with this document on mount instead of the autosaved draft — e.g. a template fetched from your own backend. */
|
|
607
611
|
initialDocument?: FormDocument;
|
|
612
|
+
/** Which mode the widget mounts into. Default "build". Pair with `features.previewMode: false` to lock a consumer to just one mode with no tabs. */
|
|
613
|
+
initialMode?: "build" | "preview";
|
|
614
|
+
/** Fires on mount and on every Build/Preview toggle — lets a host mirror the current mode without its own tab UI. */
|
|
615
|
+
onModeChange?: (mode: "build" | "preview") => void;
|
|
608
616
|
}
|
|
609
617
|
|
|
610
618
|
declare const FormBuilder: react.ForwardRefExoticComponent<FormBuilderProps & react.RefAttributes<FormBuilderHandle>>;
|
package/dist/index.d.ts
CHANGED
|
@@ -592,6 +592,10 @@ interface FormBuilderFeatures {
|
|
|
592
592
|
sections?: boolean;
|
|
593
593
|
/** Drag-to-reorder fields within a section. Default true. */
|
|
594
594
|
dragReorder?: boolean;
|
|
595
|
+
/** Laptop/Tablet/Mobile width switcher above the Preview canvas. Default true. */
|
|
596
|
+
deviceToggle?: boolean;
|
|
597
|
+
/** Caps the number of input-type fields (not content blocks) addable across the whole document. Default undefined (unlimited). */
|
|
598
|
+
maxFields?: number;
|
|
595
599
|
}
|
|
596
600
|
interface FormBuilderProps {
|
|
597
601
|
theme?: Partial<Theme>;
|
|
@@ -605,6 +609,10 @@ interface FormBuilderProps {
|
|
|
605
609
|
onSubmit?: (payload: SubmitPayload) => void;
|
|
606
610
|
/** Seeds the builder with this document on mount instead of the autosaved draft — e.g. a template fetched from your own backend. */
|
|
607
611
|
initialDocument?: FormDocument;
|
|
612
|
+
/** Which mode the widget mounts into. Default "build". Pair with `features.previewMode: false` to lock a consumer to just one mode with no tabs. */
|
|
613
|
+
initialMode?: "build" | "preview";
|
|
614
|
+
/** Fires on mount and on every Build/Preview toggle — lets a host mirror the current mode without its own tab UI. */
|
|
615
|
+
onModeChange?: (mode: "build" | "preview") => void;
|
|
608
616
|
}
|
|
609
617
|
|
|
610
618
|
declare const FormBuilder: react.ForwardRefExoticComponent<FormBuilderProps & react.RefAttributes<FormBuilderHandle>>;
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,70 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
// src/FormBuilder.tsx
|
|
4
|
-
import { forwardRef, useImperativeHandle, useState as useState8 } from "react";
|
|
4
|
+
import { forwardRef, useEffect as useEffect2, useImperativeHandle, useState as useState8 } from "react";
|
|
5
5
|
import { Loader2 as Loader22, Menu, SlidersHorizontal } from "lucide-react";
|
|
6
6
|
|
|
7
|
+
// src/constants/fieldTypes.ts
|
|
8
|
+
import {
|
|
9
|
+
Type,
|
|
10
|
+
AlignLeft,
|
|
11
|
+
ChevronDown,
|
|
12
|
+
Circle,
|
|
13
|
+
ListChecks,
|
|
14
|
+
CheckSquare,
|
|
15
|
+
ToggleLeft,
|
|
16
|
+
FileText,
|
|
17
|
+
Image as ImageIcon,
|
|
18
|
+
MousePointerClick
|
|
19
|
+
} from "lucide-react";
|
|
20
|
+
var FIELD_TYPES = [
|
|
21
|
+
{ type: "paragraph", icon: FileText, isContent: true, defaultIcon: "FileText" },
|
|
22
|
+
{ type: "image", icon: ImageIcon, isContent: true, isImage: true, defaultIcon: "Type" },
|
|
23
|
+
{ type: "button", icon: MousePointerClick, isContent: true, defaultIcon: "Type" },
|
|
24
|
+
{ type: "input", icon: Type, placeholder: true, hasSubtype: true, defaultIcon: "Type" },
|
|
25
|
+
{ type: "textarea", icon: AlignLeft, placeholder: true, defaultIcon: "AlignLeft" },
|
|
26
|
+
{ type: "select", icon: ChevronDown, options: true, defaultIcon: "ChevronDown" },
|
|
27
|
+
{ type: "radio", icon: Circle, options: true, defaultIcon: "Circle" },
|
|
28
|
+
{ type: "checkboxGroup", icon: ListChecks, options: true, multiValue: true, defaultIcon: "ListChecks" },
|
|
29
|
+
{ type: "checkbox", icon: CheckSquare, boolean: true, defaultIcon: "CheckSquare" },
|
|
30
|
+
{ type: "toggle", icon: ToggleLeft, boolean: true, defaultIcon: "ToggleLeft" }
|
|
31
|
+
];
|
|
32
|
+
var TYPE_MAP = Object.fromEntries(FIELD_TYPES.map((f) => [f.type, f]));
|
|
33
|
+
var CONTENT_TYPES = FIELD_TYPES.filter((f) => f.isContent);
|
|
34
|
+
var FORM_TYPES = FIELD_TYPES.filter((f) => !f.isContent);
|
|
35
|
+
function getMeta(type) {
|
|
36
|
+
return TYPE_MAP[type] || TYPE_MAP.input;
|
|
37
|
+
}
|
|
38
|
+
var LEGACY_INPUT_TYPES = ["text", "email", "phone", "number", "password", "date"];
|
|
39
|
+
var INPUT_SUBTYPES = ["text", "email", "phone", "number", "password", "date", "time"];
|
|
40
|
+
var IMAGE_SHAPES = ["square", "circle", "banner"];
|
|
41
|
+
var FIELD_TYPE_CHROME_KEY = {
|
|
42
|
+
paragraph: "fieldTypeParagraph",
|
|
43
|
+
image: "fieldTypeImage",
|
|
44
|
+
button: "fieldTypeButton",
|
|
45
|
+
input: "fieldTypeInput",
|
|
46
|
+
textarea: "fieldTypeTextarea",
|
|
47
|
+
select: "fieldTypeSelect",
|
|
48
|
+
radio: "fieldTypeRadio",
|
|
49
|
+
checkboxGroup: "fieldTypeCheckboxGroup",
|
|
50
|
+
checkbox: "fieldTypeCheckbox",
|
|
51
|
+
toggle: "fieldTypeToggle"
|
|
52
|
+
};
|
|
53
|
+
var INPUT_SUBTYPE_CHROME_KEY = {
|
|
54
|
+
text: "subtypeText",
|
|
55
|
+
email: "subtypeEmail",
|
|
56
|
+
phone: "subtypePhone",
|
|
57
|
+
number: "subtypeNumber",
|
|
58
|
+
password: "subtypePassword",
|
|
59
|
+
date: "subtypeDate",
|
|
60
|
+
time: "subtypeTime"
|
|
61
|
+
};
|
|
62
|
+
var IMAGE_SHAPE_CHROME_KEY = {
|
|
63
|
+
square: "shapeSquare",
|
|
64
|
+
circle: "shapeCircle",
|
|
65
|
+
banner: "shapeBanner"
|
|
66
|
+
};
|
|
67
|
+
|
|
7
68
|
// src/i18n/languages.ts
|
|
8
69
|
var DEFAULT_LANGUAGES = [
|
|
9
70
|
{ code: "en", label: "EN" },
|
|
@@ -391,67 +452,6 @@ var localStorageAdapter = {
|
|
|
391
452
|
}
|
|
392
453
|
};
|
|
393
454
|
|
|
394
|
-
// src/constants/fieldTypes.ts
|
|
395
|
-
import {
|
|
396
|
-
Type,
|
|
397
|
-
AlignLeft,
|
|
398
|
-
ChevronDown,
|
|
399
|
-
Circle,
|
|
400
|
-
ListChecks,
|
|
401
|
-
CheckSquare,
|
|
402
|
-
ToggleLeft,
|
|
403
|
-
FileText,
|
|
404
|
-
Image as ImageIcon,
|
|
405
|
-
MousePointerClick
|
|
406
|
-
} from "lucide-react";
|
|
407
|
-
var FIELD_TYPES = [
|
|
408
|
-
{ type: "paragraph", icon: FileText, isContent: true, defaultIcon: "FileText" },
|
|
409
|
-
{ type: "image", icon: ImageIcon, isContent: true, isImage: true, defaultIcon: "Type" },
|
|
410
|
-
{ type: "button", icon: MousePointerClick, isContent: true, defaultIcon: "Type" },
|
|
411
|
-
{ type: "input", icon: Type, placeholder: true, hasSubtype: true, defaultIcon: "Type" },
|
|
412
|
-
{ type: "textarea", icon: AlignLeft, placeholder: true, defaultIcon: "AlignLeft" },
|
|
413
|
-
{ type: "select", icon: ChevronDown, options: true, defaultIcon: "ChevronDown" },
|
|
414
|
-
{ type: "radio", icon: Circle, options: true, defaultIcon: "Circle" },
|
|
415
|
-
{ type: "checkboxGroup", icon: ListChecks, options: true, multiValue: true, defaultIcon: "ListChecks" },
|
|
416
|
-
{ type: "checkbox", icon: CheckSquare, boolean: true, defaultIcon: "CheckSquare" },
|
|
417
|
-
{ type: "toggle", icon: ToggleLeft, boolean: true, defaultIcon: "ToggleLeft" }
|
|
418
|
-
];
|
|
419
|
-
var TYPE_MAP = Object.fromEntries(FIELD_TYPES.map((f) => [f.type, f]));
|
|
420
|
-
var CONTENT_TYPES = FIELD_TYPES.filter((f) => f.isContent);
|
|
421
|
-
var FORM_TYPES = FIELD_TYPES.filter((f) => !f.isContent);
|
|
422
|
-
function getMeta(type) {
|
|
423
|
-
return TYPE_MAP[type] || TYPE_MAP.input;
|
|
424
|
-
}
|
|
425
|
-
var LEGACY_INPUT_TYPES = ["text", "email", "phone", "number", "password", "date"];
|
|
426
|
-
var INPUT_SUBTYPES = ["text", "email", "phone", "number", "password", "date", "time"];
|
|
427
|
-
var IMAGE_SHAPES = ["square", "circle", "banner"];
|
|
428
|
-
var FIELD_TYPE_CHROME_KEY = {
|
|
429
|
-
paragraph: "fieldTypeParagraph",
|
|
430
|
-
image: "fieldTypeImage",
|
|
431
|
-
button: "fieldTypeButton",
|
|
432
|
-
input: "fieldTypeInput",
|
|
433
|
-
textarea: "fieldTypeTextarea",
|
|
434
|
-
select: "fieldTypeSelect",
|
|
435
|
-
radio: "fieldTypeRadio",
|
|
436
|
-
checkboxGroup: "fieldTypeCheckboxGroup",
|
|
437
|
-
checkbox: "fieldTypeCheckbox",
|
|
438
|
-
toggle: "fieldTypeToggle"
|
|
439
|
-
};
|
|
440
|
-
var INPUT_SUBTYPE_CHROME_KEY = {
|
|
441
|
-
text: "subtypeText",
|
|
442
|
-
email: "subtypeEmail",
|
|
443
|
-
phone: "subtypePhone",
|
|
444
|
-
number: "subtypeNumber",
|
|
445
|
-
password: "subtypePassword",
|
|
446
|
-
date: "subtypeDate",
|
|
447
|
-
time: "subtypeTime"
|
|
448
|
-
};
|
|
449
|
-
var IMAGE_SHAPE_CHROME_KEY = {
|
|
450
|
-
square: "shapeSquare",
|
|
451
|
-
circle: "shapeCircle",
|
|
452
|
-
banner: "shapeBanner"
|
|
453
|
-
};
|
|
454
|
-
|
|
455
455
|
// src/lib/id.ts
|
|
456
456
|
var genFormId = () => `form_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 7)}`;
|
|
457
457
|
var genSectionId = () => `section_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 7)}`;
|
|
@@ -629,7 +629,8 @@ var DEFAULT_FEATURES = {
|
|
|
629
629
|
contentBlocks: true,
|
|
630
630
|
fieldTypes: true,
|
|
631
631
|
sections: true,
|
|
632
|
-
dragReorder: true
|
|
632
|
+
dragReorder: true,
|
|
633
|
+
deviceToggle: true
|
|
633
634
|
};
|
|
634
635
|
function resolveFeatures(features) {
|
|
635
636
|
return {
|
|
@@ -1393,11 +1394,13 @@ function Palette({
|
|
|
1393
1394
|
theme,
|
|
1394
1395
|
updateThemeColor,
|
|
1395
1396
|
updateThemeLayout,
|
|
1396
|
-
resetTheme
|
|
1397
|
+
resetTheme,
|
|
1398
|
+
fieldCount
|
|
1397
1399
|
}) {
|
|
1398
1400
|
const [tab, setTab] = useState5("blocks");
|
|
1399
1401
|
const contentTypes = CONTENT_TYPES.filter((f) => isContentBlockEnabled(features, f.type));
|
|
1400
1402
|
const formTypes = FORM_TYPES.filter((f) => isFieldTypeEnabled(features, f.type));
|
|
1403
|
+
const atFieldCap = typeof features.maxFields === "number" && fieldCount >= features.maxFields;
|
|
1401
1404
|
return /* @__PURE__ */ jsxs5("div", { className: "fb-palette", style: styles.palette, children: [
|
|
1402
1405
|
features.design && /* @__PURE__ */ jsx5("div", { style: { marginBottom: 14 }, children: /* @__PURE__ */ jsx5(
|
|
1403
1406
|
Segmented,
|
|
@@ -1440,7 +1443,8 @@ function Palette({
|
|
|
1440
1443
|
/* @__PURE__ */ jsx5("div", { style: { ...styles.panelHeading, marginTop: 16 }, children: chrome.formFields }),
|
|
1441
1444
|
/* @__PURE__ */ jsx5("div", { style: styles.paletteList, children: formTypes.map((f) => {
|
|
1442
1445
|
const Icon = f.icon;
|
|
1443
|
-
|
|
1446
|
+
const itemStyle = atFieldCap ? { ...styles.paletteItem, opacity: 0.45, cursor: "not-allowed" } : styles.paletteItem;
|
|
1447
|
+
return /* @__PURE__ */ jsxs5("button", { type: "button", disabled: atFieldCap, style: itemStyle, onClick: () => onAddField(f.type), children: [
|
|
1444
1448
|
/* @__PURE__ */ jsx5(Icon, { size: 16, color: "var(--fb-primary)" }),
|
|
1445
1449
|
/* @__PURE__ */ jsx5("span", { children: chrome[FIELD_TYPE_CHROME_KEY[f.type]] }),
|
|
1446
1450
|
/* @__PURE__ */ jsx5(Plus, { size: 13, color: "var(--fb-muted)", style: { marginLeft: "auto" } })
|
|
@@ -2171,7 +2175,7 @@ function collectFieldValues(fields) {
|
|
|
2171
2175
|
|
|
2172
2176
|
// src/components/PreviewPane.tsx
|
|
2173
2177
|
import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
2174
|
-
function PreviewPane({ title, sections, onFieldChange, language, strings, chrome, baseMaxWidth, onSubmit }) {
|
|
2178
|
+
function PreviewPane({ title, sections, onFieldChange, language, strings, chrome, baseMaxWidth, features, onSubmit }) {
|
|
2175
2179
|
const [device, setDevice] = useState6("laptop");
|
|
2176
2180
|
const [errors, setErrors] = useState6({});
|
|
2177
2181
|
const [submitted, setSubmitted] = useState6(null);
|
|
@@ -2234,7 +2238,7 @@ function PreviewPane({ title, sections, onFieldChange, language, strings, chrome
|
|
|
2234
2238
|
}
|
|
2235
2239
|
return /* @__PURE__ */ jsxs12("div", { style: styles.previewWrap, children: [
|
|
2236
2240
|
/* @__PURE__ */ jsxs12("div", { style: { width: "100%", maxWidth, display: "flex", flexDirection: "column", alignItems: "center" }, children: [
|
|
2237
|
-
/* @__PURE__ */ jsx14("div", { style: styles.previewToolbar, children: /* @__PURE__ */ jsx14(Segmented, { options: deviceOptions, value: device, onChange: setDevice }) }),
|
|
2241
|
+
features.deviceToggle && /* @__PURE__ */ jsx14("div", { style: styles.previewToolbar, children: /* @__PURE__ */ jsx14(Segmented, { options: deviceOptions, value: device, onChange: setDevice }) }),
|
|
2238
2242
|
/* @__PURE__ */ jsxs12("div", { style: { ...styles.previewCard, maxWidth, width: "100%" }, children: [
|
|
2239
2243
|
/* @__PURE__ */ jsx14("h2", { style: styles.previewTitle, children: t(title, language) }),
|
|
2240
2244
|
allFields.length === 0 && /* @__PURE__ */ jsx14("p", { style: { color: "var(--fb-muted)", fontSize: 14 }, children: strings.addFieldsHint }),
|
|
@@ -2405,7 +2409,9 @@ var FormBuilder = forwardRef(function FormBuilder2({
|
|
|
2405
2409
|
features: featuresProp,
|
|
2406
2410
|
storage: storageProp,
|
|
2407
2411
|
onSubmit,
|
|
2408
|
-
initialDocument
|
|
2412
|
+
initialDocument,
|
|
2413
|
+
initialMode,
|
|
2414
|
+
onModeChange
|
|
2409
2415
|
}, ref) {
|
|
2410
2416
|
const features = resolveFeatures(featuresProp);
|
|
2411
2417
|
const storage = storageProp ?? localStorageAdapter;
|
|
@@ -2415,7 +2421,14 @@ var FormBuilder = forwardRef(function FormBuilder2({
|
|
|
2415
2421
|
const chrome = { ...CHROME[language] || CHROME.en, ...chromeOverride && chromeOverride[language] || {} };
|
|
2416
2422
|
const doc = useFormDocument({ language, chrome });
|
|
2417
2423
|
const drag = useDragReorder(doc.reorderWithinSection);
|
|
2418
|
-
const [mode,
|
|
2424
|
+
const [mode, setModeState] = useState8(initialMode ?? "build");
|
|
2425
|
+
const setMode = (next) => {
|
|
2426
|
+
setModeState(next);
|
|
2427
|
+
onModeChange?.(next);
|
|
2428
|
+
};
|
|
2429
|
+
useEffect2(() => {
|
|
2430
|
+
onModeChange?.(mode);
|
|
2431
|
+
}, []);
|
|
2419
2432
|
const [showJson, setShowJson] = useState8(false);
|
|
2420
2433
|
const [showLibrary, setShowLibrary] = useState8(false);
|
|
2421
2434
|
const [copied, setCopied] = useState8(false);
|
|
@@ -2462,6 +2475,7 @@ var FormBuilder = forwardRef(function FormBuilder2({
|
|
|
2462
2475
|
}
|
|
2463
2476
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2464
2477
|
}), [jsonDoc, jsonString]);
|
|
2478
|
+
const fieldCount = doc.sections.reduce((n, s) => n + s.fields.filter((f) => !getMeta(f.type).isContent).length, 0);
|
|
2465
2479
|
const activeSectionIdx = doc.sections.findIndex((s) => s.id === doc.activeSection?.id);
|
|
2466
2480
|
const activeSectionLabel = t(doc.activeSection?.title, language) || `#${activeSectionIdx + 1}`;
|
|
2467
2481
|
const rootStyle = {
|
|
@@ -2553,6 +2567,7 @@ var FormBuilder = forwardRef(function FormBuilder2({
|
|
|
2553
2567
|
setMobilePanel("none");
|
|
2554
2568
|
},
|
|
2555
2569
|
features,
|
|
2570
|
+
fieldCount,
|
|
2556
2571
|
theme,
|
|
2557
2572
|
updateThemeColor,
|
|
2558
2573
|
updateThemeLayout,
|
|
@@ -2616,6 +2631,7 @@ var FormBuilder = forwardRef(function FormBuilder2({
|
|
|
2616
2631
|
strings,
|
|
2617
2632
|
chrome,
|
|
2618
2633
|
baseMaxWidth: theme.layout.maxWidth,
|
|
2634
|
+
features,
|
|
2619
2635
|
onSubmit
|
|
2620
2636
|
}
|
|
2621
2637
|
),
|