form-page-builder 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -16
- package/dist/index.cjs +561 -193
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +94 -16
- package/dist/index.d.ts +94 -16
- package/dist/index.js +568 -207
- package/dist/index.js.map +1 -1
- package/package.json +12 -2
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ Embeddable, bilingual (EN/JA by default, extensible), drag-and-drop form builder
|
|
|
10
10
|
|
|
11
11
|
## Examples
|
|
12
12
|
|
|
13
|
-
The [live demo](https://mainsolutioncoltd.github.io/form-page-builder/) is a gallery of
|
|
13
|
+
The [live demo](https://mainsolutioncoltd.github.io/form-page-builder/) is a gallery of `<FormBuilder />` configurations, each showing a different realistic way to compose `features`/`theme`/`storage`/`initialDocument`/`language` — jump straight to one. Its sticky header (its headings/captions, not the widget's own EN/JA switcher) has an EN/日本語 toggle, a Light/Dark toggle that swaps every example except "Branded" (badged "Fixed theme", since its whole point is a locked theme) between `DEFAULT_THEME` and `DARK_THEME`, and links back to this repo and the npm package.
|
|
14
14
|
|
|
15
15
|
| Example | What it shows |
|
|
16
16
|
|---|---|
|
|
@@ -20,8 +20,9 @@ The [live demo](https://mainsolutioncoltd.github.io/form-page-builder/) is a gal
|
|
|
20
20
|
| [Survey builder](https://mainsolutioncoltd.github.io/form-page-builder/#survey) | `fieldTypes`/`contentBlocks` allowlists plus `onSubmit` — a single-shape survey embed that hands you the answers. |
|
|
21
21
|
| [Locked-structure form](https://mainsolutioncoltd.github.io/form-page-builder/#locked-structure) | `initialDocument` seeds a fixed field set; structural toggles are off so only styling/labels stay editable. |
|
|
22
22
|
| [Localized (French)](https://mainsolutioncoltd.github.io/form-page-builder/#localized) | `language`/`languages`/`strings`/`chrome` — a language beyond the built-in EN/JA, partially translated. |
|
|
23
|
+
| [Template roles](https://mainsolutioncoltd.github.io/form-page-builder/#template-roles) | Two instances sharing one template library — `templates: { manage: true, max: 3 }` (curate) next to `{ manage: false }` (apply-only), plus copy/paste between them. |
|
|
23
24
|
|
|
24
|
-
The source for all
|
|
25
|
+
The source for all of them lives in [dev/main.tsx](./dev/main.tsx) — `npm run dev` runs the same gallery locally against `src/` directly, and is the fastest way to try a `features`/`theme` combination before wiring it into your app.
|
|
25
26
|
|
|
26
27
|
**This is a builder + viewer, not a data handler.** It builds and previews a JSON *schema* describing a form's fields, sections, and layout blocks (including plain content blocks like paragraphs and images, not just inputs). Preview mode's "Submit" validates and shows a mock "here's what would be sent to your backend" modal, and — if you pass `onSubmit` — hands you the entered values too; either way, this package never sends or stores them itself. The only thing it persists on its own is the *builder's own* draft/Templates state (via the pluggable `StorageAdapter` below); actually delivering submissions to a backend is up to the host app.
|
|
27
28
|
|
|
@@ -70,8 +71,12 @@ Above ~720px wide, Build mode is the usual three-column Palette/Canvas/Inspector
|
|
|
70
71
|
| `storage` | `StorageAdapter` | Pluggable persistence backend for the builder's own draft/Templates library — see below. |
|
|
71
72
|
| `onSubmit` | `(payload: SubmitPayload) => void` | Called when Preview mode's Submit button is clicked and validation passes — see below. |
|
|
72
73
|
| `initialDocument` | `FormDocument` | Seeds the builder with this document on mount instead of the autosaved draft — see "Programmatic integration" below. |
|
|
74
|
+
| `initialMode` | `"build" \| "preview"` | Which mode the widget mounts into (default `"build"`). Pair with `features.previewMode: false` to lock a consumer to just one mode with no tabs — e.g. a fill-only embed that never shows the Build canvas. Or leave `previewMode` on and pick `initialMode` per document (e.g. `"preview"` once a document already has saved data) so the widget opens on the right mode while still letting the built-in tabs switch it. |
|
|
75
|
+
| `onModeChange` | `(mode: "build" \| "preview") => void` | Fires on mount and on every Build/Preview toggle. Lets a host mirror the current mode (e.g. to show its own Save button only in Build mode) without building a separate tab UI around the widget. |
|
|
76
|
+
| `onTemplateChange` | `(change: TemplateChange) => void` | Fires when a template is created (`source: "new"`), overwritten (`"saved"`), applied as the working document (`"applied"`), or deleted (`"deleted"`). `change` is `{ id: string \| null; title: string; source }`. Useful for syncing a host's own state or backend index. |
|
|
77
|
+
| `templateClipboardKey` | `string` | Overrides the localStorage key behind "Copy template" / "Paste template" (default `"form-page-builder:clipboard"`). Instances sharing a key can copy/paste between each other. |
|
|
73
78
|
|
|
74
|
-
A `ref` on `<FormBuilder />` gives you a `FormBuilderHandle` (`getDocument()` / `loadDocument()` / `exportJson()`) — see "Programmatic integration" below.
|
|
79
|
+
A `ref` on `<FormBuilder />` gives you a `FormBuilderHandle` (`getDocument()` / `loadDocument()` / `exportJson()` / `getTemplate()` / `loadTemplate()`) — see "Programmatic integration" and "Copy / paste templates" below.
|
|
75
80
|
|
|
76
81
|
### Features: `features`
|
|
77
82
|
|
|
@@ -102,18 +107,22 @@ Every optional UI surface can be switched on or off independently through one `f
|
|
|
102
107
|
| Key | Type | Default | Controls |
|
|
103
108
|
|---|---|---|---|
|
|
104
109
|
| `naming` | `boolean` | `true` | The editable form-title input in the toolbar. |
|
|
105
|
-
| `templates` | `boolean` | `true` | The Templates library (browse/
|
|
110
|
+
| `templates` | `boolean \| { manage?: boolean; max?: number }` | `true` | The Templates library and the "Save" button. `true` = full library (browse / apply / save / overwrite / delete). `false` = no template UI. `{ manage: false }` = pick-and-apply only: the library lists templates and the user can apply one as a starting point, but can't create, overwrite, or delete them and the "Save" button is hidden. `{ max: n }` caps how many templates can be stored (default 5). |
|
|
106
111
|
| `newForm` | `boolean` | `true` | The "New Form" reset button. |
|
|
107
112
|
| `autosave` | `boolean` | `true` | Autosaving the draft to `storage`. The initial draft *load* always happens regardless — this only gates the write path. |
|
|
108
113
|
| `jsonView` | `boolean` | `true` | The "View JSON" button and modal. |
|
|
109
|
-
| `
|
|
114
|
+
| `templateClipboard` | `boolean` | `true` | The "Copy template" / "Paste template" icon buttons next to "View JSON". Paste is enabled only once another builder instance in the same browser has copied one. |
|
|
115
|
+
| `previewMode` | `boolean` | `true` | The Build/Preview tabs. When `false`, the tabs are hidden and the builder stays in whichever mode it started in (`initialMode`, Build by default). |
|
|
110
116
|
| `languageSwitcher` | `boolean` | `true` | The language-switcher pill in the toolbar. |
|
|
111
117
|
| `design` | `boolean` | `false` | The global "Design" tab (colors/spacing), i.e. the old `themeEditable` prop. |
|
|
112
118
|
| `blockStyling` | `boolean` | `true` | Per-field styling controls: paragraph heading/font/color, button color — independent of `design`. |
|
|
113
119
|
| `contentBlocks` | `boolean \| ("paragraph" \| "image" \| "button")[]` | `true` | Which content blocks can be *added* from the palette. `true` = all, `false` = none, or an allowlist. |
|
|
114
120
|
| `fieldTypes` | `boolean \| ("input" \| "textarea" \| "select" \| "radio" \| "checkboxGroup" \| "checkbox" \| "toggle")[]` | `true` | Which form field types can be *added* from the palette. `true` = all, `false` = none, or an allowlist. |
|
|
115
|
-
| `sections` | `boolean` | `true` | Add/duplicate/move/delete-section controls and the section
|
|
121
|
+
| `sections` | `boolean` | `true` | Add/duplicate/move/delete-section controls and the "Add section" button. |
|
|
122
|
+
| `sectionBackground` | `boolean` | inherits `sections` | Per-section background-color swatches + custom-color picker in the section header. Independent of `sections` — set it `false` to lock section backgrounds while keeping the structural controls, or leave it unset and it follows whatever `sections` is. |
|
|
116
123
|
| `dragReorder` | `boolean` | `true` | Drag-to-reorder fields within a section. |
|
|
124
|
+
| `deviceToggle` | `boolean` | `true` | The Laptop/Tablet/Mobile width switcher above the Preview canvas. When `false`, Preview always renders at the Laptop (full) width. |
|
|
125
|
+
| `maxFields` | `number` | `undefined` (unlimited) | Caps the total number of input-type fields (not content blocks) addable across the whole document. Once reached, the Form Fields palette buttons disable until a field is removed. |
|
|
117
126
|
|
|
118
127
|
Disabling `contentBlocks`/`fieldTypes` for a given type only hides it from the palette going forward — if a document loaded via `initialDocument` (or a saved template) already contains fields of a now-disabled type, they still render and remain editable in Build mode; nothing is stripped or hidden.
|
|
119
128
|
|
|
@@ -135,7 +144,7 @@ import { FormBuilder, DARK_THEME } from "form-page-builder";
|
|
|
135
144
|
|
|
136
145
|
### Persistence: `StorageAdapter`
|
|
137
146
|
|
|
138
|
-
By default the component autosaves a draft, plus a "Templates" library (up to 5 saved forms, shown via the toolbar's Templates button), to `window.localStorage`. Being local-storage-only means neither persists across devices or browsers. To persist the builder's state to your own backend (a Next.js API route, a PHP endpoint, etc.) instead — so drafts and templates are shared across devices, and your backend can populate/manage the template list itself — implement and pass a `StorageAdapter`. Its `get`/`set`/`delete` calls *are* the create/update/delete hooks: whatever your implementation does inside them (write to a database, call your API) runs on every template save/update/delete
|
|
147
|
+
By default the component autosaves a draft, plus a "Templates" library (up to 5 saved forms, shown via the toolbar's Templates button), to `window.localStorage`. Being local-storage-only means neither persists across devices or browsers. To persist the builder's state to your own backend (a Next.js API route, a PHP endpoint, etc.) instead — so drafts and templates are shared across devices, and your backend can populate/manage the template list itself — implement and pass a `StorageAdapter`. Its `get`/`set`/`delete` calls *are* the create/update/delete hooks: whatever your implementation does inside them (write to a database, call your API) runs on every template save/update/delete. `get`/`set` may be async — the Templates modal shows a spinner while a load is in flight and an inline error if the adapter throws or rejects — and `onTemplateChange` (see the props table) fires alongside so a host can keep its own index in sync.
|
|
139
148
|
|
|
140
149
|
```ts
|
|
141
150
|
interface StorageAdapter {
|
|
@@ -145,23 +154,33 @@ interface StorageAdapter {
|
|
|
145
154
|
}
|
|
146
155
|
```
|
|
147
156
|
|
|
157
|
+
The adapter is called with three kinds of key, exported so you can route them to your own REST endpoints without hardcoding the strings: `DRAFT_KEY` (the autosaved draft), `INDEX_KEY` (the template list — a JSON `SavedFormMeta[]`), and `formKey(id)` (one saved template). `savedFormId(key)` returns the id for a per-template key, else `null`.
|
|
158
|
+
|
|
148
159
|
```tsx
|
|
149
|
-
import {
|
|
160
|
+
import {
|
|
161
|
+
FormBuilder, type StorageAdapter,
|
|
162
|
+
DRAFT_KEY, INDEX_KEY, savedFormId,
|
|
163
|
+
} from "form-page-builder";
|
|
150
164
|
|
|
151
165
|
const apiStorage: StorageAdapter = {
|
|
152
166
|
async get(key) {
|
|
153
|
-
|
|
154
|
-
if (
|
|
155
|
-
|
|
167
|
+
if (key === DRAFT_KEY) return localStorage.getItem(key); // keep the draft local
|
|
168
|
+
if (key === INDEX_KEY) {
|
|
169
|
+
const res = await fetch("/api/form-templates");
|
|
170
|
+
return res.ok ? JSON.stringify(await res.json()) : null;
|
|
171
|
+
}
|
|
172
|
+
const id = savedFormId(key);
|
|
173
|
+
const res = await fetch(`/api/form-templates/${id}`);
|
|
174
|
+
return res.ok ? JSON.stringify(await res.json()) : null;
|
|
156
175
|
},
|
|
157
176
|
async set(key, value) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
});
|
|
177
|
+
if (key === DRAFT_KEY) return void localStorage.setItem(key, value);
|
|
178
|
+
if (key === INDEX_KEY) return; // index is re-derived from the records on read
|
|
179
|
+
await fetch(`/api/form-templates/${savedFormId(key)}`, { method: "PUT", body: value });
|
|
162
180
|
},
|
|
163
181
|
async delete(key) {
|
|
164
|
-
|
|
182
|
+
const id = savedFormId(key);
|
|
183
|
+
if (id) await fetch(`/api/form-templates/${id}`, { method: "DELETE" });
|
|
165
184
|
},
|
|
166
185
|
};
|
|
167
186
|
|
|
@@ -221,6 +240,24 @@ function BuilderPage() {
|
|
|
221
240
|
}
|
|
222
241
|
```
|
|
223
242
|
|
|
243
|
+
### Copy / paste templates
|
|
244
|
+
|
|
245
|
+
The toolbar's "Copy template" / "Paste template" icons (next to "View JSON", toggle with `features.templateClipboard`) let a user copy the current form from one builder and paste it into another — across pages, tabs, and reloads in the same browser. Copy writes a portable envelope to a localStorage key (`templateClipboardKey`, default `"form-page-builder:clipboard"`); every mounted builder watches that key, so Paste lights up everywhere as soon as something is copied. Paste asks for confirmation, then replaces the working document.
|
|
246
|
+
|
|
247
|
+
The same portable object is available programmatically — use it to hand a template to your own storage, or to move one between builders without the clipboard:
|
|
248
|
+
|
|
249
|
+
```tsx
|
|
250
|
+
import { FormBuilder, serializeTemplate, parseTemplate, type FormBuilderHandle } from "form-page-builder";
|
|
251
|
+
|
|
252
|
+
// via the ref handle
|
|
253
|
+
const tpl = ref.current.getTemplate(); // { __fpb: "template", v: 1, document }
|
|
254
|
+
ref.current.loadTemplate(tpl); // accepts the object or its JSON string; returns false if unparseable
|
|
255
|
+
|
|
256
|
+
// or the standalone helpers (e.g. to persist to your backend)
|
|
257
|
+
const json = serializeTemplate(ref.current.getDocument());
|
|
258
|
+
const doc = parseTemplate(json); // also accepts bare "View JSON" output; null if not a document
|
|
259
|
+
```
|
|
260
|
+
|
|
224
261
|
## Using in a plain HTML page (no bundler)
|
|
225
262
|
|
|
226
263
|
There's intentionally no UMD/IIFE global-script build (see below), but you don't need a bundler to use this package — a browser-native ES module setup works too, via an [import map](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap) and a CDN like [esm.sh](https://esm.sh/):
|