form-page-builder 0.1.0 → 1.0.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/LICENSE +0 -0
- package/README.md +100 -8
- package/dist/index.cjs +846 -742
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +92 -31
- package/dist/index.d.ts +92 -31
- package/dist/index.js +846 -741
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/LICENSE
CHANGED
|
File without changes
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
Embeddable, bilingual (EN/JA by default, extensible), drag-and-drop form builder widget for React. Ships a single `<FormBuilder />` component with a Build mode (drag/drop canvas, field inspector) and a Preview mode (responsive, validating runtime form), plus a JSON export of the resulting document.
|
|
10
10
|
|
|
11
|
-
**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).
|
|
11
|
+
**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.
|
|
12
12
|
|
|
13
13
|
## Install
|
|
14
14
|
|
|
@@ -32,21 +32,31 @@ export default function BuilderPage() {
|
|
|
32
32
|
|
|
33
33
|
In Next.js App Router, the package's entry already carries a `"use client"` directive, so it can be imported directly from a Server Component tree without you adding the directive yourself.
|
|
34
34
|
|
|
35
|
+
### Sizing
|
|
36
|
+
|
|
37
|
+
The widget caps itself at the viewport height (`100vh`, upgrading to `100dvh` on browsers that support it) and scrolls its own Palette/Canvas/Inspector panels internally past that — it never grows taller than the space available. Give its wrapping container an explicit height (e.g. `height: "100dvh"` for a full-height layout on mobile, or any fixed/`%` height) if you want it to fill that space; without one, it just sizes to its content and the page scrolls normally, either way with a single scrollbar.
|
|
38
|
+
|
|
39
|
+
If you *do* size a wrapper to exactly `100vh`/`100dvh` and still see the page itself scroll by a few extra pixels, that's almost always the browser's default `<body>` margin (commonly `8px`) adding to that full-viewport height — reset it yourself (`body { margin: 0; }`), same as you would for any other full-height layout; this package doesn't touch your page's global styles.
|
|
40
|
+
|
|
35
41
|
### Props
|
|
36
42
|
|
|
37
43
|
| Prop | Type | Description |
|
|
38
44
|
|---|---|---|
|
|
39
45
|
| `theme` | `Partial<Theme>` | Override default colors/layout spacing. |
|
|
40
|
-
| `themeEditable` | `boolean` | Show
|
|
46
|
+
| `themeEditable` | `boolean` | Show the in-app "Design" tab (theme/spacing/submit-button controls) in the left sidebar. |
|
|
41
47
|
| `language` | `string` | Initial builder language (defaults to the first entry in `languages`). |
|
|
42
48
|
| `languages` | `{ code: string; label: string }[]` | Language switcher options (default: EN/JA). |
|
|
43
49
|
| `strings` | partial override of runtime/validation strings, keyed by language code | |
|
|
44
50
|
| `chrome` | partial override of builder-UI strings, keyed by language code | |
|
|
45
|
-
| `storage` | `StorageAdapter` | Pluggable persistence backend for the builder's own draft/library — see below. |
|
|
51
|
+
| `storage` | `StorageAdapter` | Pluggable persistence backend for the builder's own draft/Templates library — see below. |
|
|
52
|
+
| `onSubmit` | `(payload: SubmitPayload) => void` | Called when Preview mode's Submit button is clicked and validation passes — see below. |
|
|
53
|
+
| `initialDocument` | `FormDocument` | Seeds the builder with this document on mount instead of the autosaved draft — see "Programmatic integration" below. |
|
|
54
|
+
|
|
55
|
+
A `ref` on `<FormBuilder />` gives you a `FormBuilderHandle` (`getDocument()` / `loadDocument()` / `exportJson()`) — see "Programmatic integration" below.
|
|
46
56
|
|
|
47
57
|
### Persistence: `StorageAdapter`
|
|
48
58
|
|
|
49
|
-
By default the component autosaves a draft
|
|
59
|
+
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, so no separate event callbacks are needed.
|
|
50
60
|
|
|
51
61
|
```ts
|
|
52
62
|
interface StorageAdapter {
|
|
@@ -79,11 +89,93 @@ const apiStorage: StorageAdapter = {
|
|
|
79
89
|
<FormBuilder storage={apiStorage} />;
|
|
80
90
|
```
|
|
81
91
|
|
|
82
|
-
This is separate from — and unrelated to — however you choose to handle real end-user form submissions in your own app; this package doesn't send or receive those.
|
|
92
|
+
This is separate from — and unrelated to — however you choose to handle real end-user form submissions in your own app; this package doesn't send or receive those on its own (see `onSubmit` below if you want Preview mode's Submit button to hand you the entered values).
|
|
93
|
+
|
|
94
|
+
### Handling submissions: `onSubmit`
|
|
95
|
+
|
|
96
|
+
A submit action lives on a **Button field** — drag one into a section from the palette (next to Paragraph/Image) and set its "When clicked" mode to Submit, with a scope of either "This section" or "Whole form". There's no document-level submit setting anymore; you can place as many buttons as you like (e.g. a per-section "Next" alongside a final "Submit", or a plain "Open link" CTA button that doesn't submit at all).
|
|
97
|
+
|
|
98
|
+
Clicking a submit-action button validates the fields in its scope and, once they pass, calls `onSubmit` with the entered values — pass this if you want to do something with them (send to your backend, log them, etc.) instead of just seeing the built-in "here's what would be sent" confirmation:
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
import { FormBuilder, type SubmitPayload } from "form-page-builder";
|
|
102
|
+
|
|
103
|
+
function handleSubmit(payload: SubmitPayload) {
|
|
104
|
+
// payload.buttonId: id of the button field that triggered this — use it to
|
|
105
|
+
// branch when a form has more than one submit button (e.g. "save draft" vs "submit")
|
|
106
|
+
// payload.scope: "form" | "section", matching that button's own scope setting
|
|
107
|
+
// payload.all: every field's raw value across the whole form, keyed by field id
|
|
108
|
+
// payload.sections: the same values, broken down section by section
|
|
109
|
+
// payload.values: just whatever was submitted (the whole form, or one section if scope is "section")
|
|
110
|
+
console.log(payload);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
<FormBuilder onSubmit={handleSubmit} />;
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Programmatic integration
|
|
117
|
+
|
|
118
|
+
Copying and pasting the "View JSON" output is fine for development, but a production integration usually wants to load and save forms through its own backend in the background instead. Two props/APIs cover that:
|
|
119
|
+
|
|
120
|
+
- `initialDocument` seeds the builder with a document (e.g. one your backend just fetched) instead of the autosaved draft.
|
|
121
|
+
- A ref exposes `getDocument()`, `loadDocument(doc)`, and `exportJson()` so you can pull the current document out (to save it yourself, on whatever schedule/event you choose) or push a new one in, independent of the `storage` autosave path:
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
import { useEffect, useRef, useState } from "react";
|
|
125
|
+
import { FormBuilder, type FormBuilderHandle, type FormDocument } from "form-page-builder";
|
|
126
|
+
|
|
127
|
+
function BuilderPage() {
|
|
128
|
+
const ref = useRef<FormBuilderHandle>(null);
|
|
129
|
+
const [initialDocument, setInitialDocument] = useState<FormDocument>();
|
|
83
130
|
|
|
84
|
-
|
|
131
|
+
useEffect(() => {
|
|
132
|
+
fetch("/api/forms/123").then((r) => r.json()).then(setInitialDocument);
|
|
133
|
+
}, []);
|
|
134
|
+
|
|
135
|
+
async function saveNow() {
|
|
136
|
+
if (!ref.current) return;
|
|
137
|
+
await fetch("/api/forms/123", { method: "PUT", body: ref.current.exportJson() });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (!initialDocument) return null;
|
|
141
|
+
return <FormBuilder ref={ref} initialDocument={initialDocument} />;
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Using in a plain HTML page (no bundler)
|
|
146
|
+
|
|
147
|
+
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/):
|
|
148
|
+
|
|
149
|
+
```html
|
|
150
|
+
<!DOCTYPE html>
|
|
151
|
+
<html>
|
|
152
|
+
<head>
|
|
153
|
+
<script type="importmap">
|
|
154
|
+
{
|
|
155
|
+
"imports": {
|
|
156
|
+
"react": "https://esm.sh/react@18",
|
|
157
|
+
"react-dom/client": "https://esm.sh/react-dom@18/client",
|
|
158
|
+
"form-page-builder": "https://esm.sh/form-page-builder?external=react,react-dom"
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
</script>
|
|
162
|
+
</head>
|
|
163
|
+
<body>
|
|
164
|
+
<div id="root"></div>
|
|
165
|
+
<script type="module">
|
|
166
|
+
import React from "react";
|
|
167
|
+
import { createRoot } from "react-dom/client";
|
|
168
|
+
import { FormBuilder } from "form-page-builder";
|
|
169
|
+
|
|
170
|
+
createRoot(document.getElementById("root")).render(
|
|
171
|
+
React.createElement(FormBuilder, {})
|
|
172
|
+
);
|
|
173
|
+
</script>
|
|
174
|
+
</body>
|
|
175
|
+
</html>
|
|
176
|
+
```
|
|
85
177
|
|
|
86
|
-
|
|
178
|
+
The `?external=react,react-dom` query on the `form-page-builder` import tells esm.sh to reuse the same `react`/`react-dom` module the page already imports, instead of bundling its own copy — so there's no duplicate-React problem, and no build step (Vite/Webpack/etc.) required. Since there's no JSX here, components are created with `React.createElement(...)` instead of `<FormBuilder />`; everything else (props, `storage` adapter, etc.) works the same as in the React/Next.js example above.
|
|
87
179
|
|
|
88
180
|
## Local development
|
|
89
181
|
|
|
@@ -102,6 +194,6 @@ Versioning and the changelog are managed by [Changesets](https://github.com/chan
|
|
|
102
194
|
1. On your PR, describe the change for consumers: `npx changeset` (pick patch/minor/major, write a summary, commit the generated file in `.changeset/`).
|
|
103
195
|
2. Once merged to `main`, a bot opens/updates a **"Version Packages"** PR that bumps `package.json` and writes `CHANGELOG.md` from the accumulated changesets ([.github/workflows/version.yml](.github/workflows/version.yml)).
|
|
104
196
|
3. Merge that PR when you're ready to ship.
|
|
105
|
-
4. Cut a [GitHub Release](https://github.com/MainSolutionCoLtd/form-page-builder/releases/new) tagged `vX.Y.Z` matching the new version — this triggers [.github/workflows/release.yml](.github/workflows/release.yml), which builds and runs `npm publish` (
|
|
197
|
+
4. Cut a [GitHub Release](https://github.com/MainSolutionCoLtd/form-page-builder/releases/new) tagged `vX.Y.Z` matching the new version — this triggers [.github/workflows/release.yml](.github/workflows/release.yml), which builds and runs `npm publish --provenance` via npm's Trusted Publishing (OIDC), so no `NPM_TOKEN` secret is needed.
|
|
106
198
|
|
|
107
199
|
Pushes to `main` also rebuild and redeploy the [live demo](https://mainsolutioncoltd.github.io/form-page-builder/) via [.github/workflows/pages.yml](.github/workflows/pages.yml).
|