@embeddables/forms 0.0.4 → 0.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 +79 -31
- package/dist/{form-DzfCc5X2.cjs → form-BOHdxeuO.cjs} +544 -216
- package/dist/form-BOHdxeuO.cjs.map +1 -0
- package/dist/{form-CbU5ezg7.d.ts → form-Br23yPDS.d.ts} +53 -14
- package/dist/form-Br23yPDS.d.ts.map +1 -0
- package/dist/{form-9Tp91g6a.js → form-Bt5pwVP6.js} +521 -217
- package/dist/form-Bt5pwVP6.js.map +1 -0
- package/dist/index-BB698udY.d.ts +78 -0
- package/dist/index-BB698udY.d.ts.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +3 -76
- package/dist/index.js +1 -1
- package/dist/react.cjs +46 -15
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.ts +24 -7
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +46 -16
- package/dist/react.js.map +1 -1
- package/dist/server.cjs +50 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.ts +13 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +49 -0
- package/dist/server.js.map +1 -0
- package/package.json +10 -5
- package/dist/form-9Tp91g6a.js.map +0 -1
- package/dist/form-CbU5ezg7.d.ts.map +0 -1
- package/dist/form-DzfCc5X2.cjs.map +0 -1
- package/dist/index.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -14,8 +14,9 @@ npm install @embeddables/forms @embeddables/core
|
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
For React apps, also install `react` (peer dependency) and wrap your tree in
|
|
17
|
-
`EmbeddablesProvider` from `@embeddables/core/react`. Register
|
|
18
|
-
|
|
17
|
+
`EmbeddablesProvider` from `@embeddables/core/react`. Register the static module
|
|
18
|
+
through `modules`, then pass its runtime configuration through the provider's
|
|
19
|
+
top-level `forms` prop.
|
|
19
20
|
|
|
20
21
|
## React
|
|
21
22
|
|
|
@@ -29,13 +30,6 @@ import { analytics } from '@embeddables/analytics/react'
|
|
|
29
30
|
import { forms, useForm, useFormField } from '@embeddables/forms/react'
|
|
30
31
|
import { config } from './embeddables/_dist/config.ts'
|
|
31
32
|
|
|
32
|
-
const schemas = {
|
|
33
|
-
signup: {
|
|
34
|
-
id: 'signup',
|
|
35
|
-
fields: [{ key: 'email', label: 'Email', type: 'email', validations: { required: true } }],
|
|
36
|
-
},
|
|
37
|
-
} as const
|
|
38
|
-
|
|
39
33
|
function SignupField() {
|
|
40
34
|
// No type arguments: the id narrows the form, and the schema map comes from
|
|
41
35
|
// the registry `em build` writes into embeddables/_dist/register.ts.
|
|
@@ -55,11 +49,21 @@ export function App() {
|
|
|
55
49
|
return (
|
|
56
50
|
<EmbeddablesProvider
|
|
57
51
|
config={config}
|
|
58
|
-
modules={[
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
52
|
+
modules={[forms(), analytics()]}
|
|
53
|
+
forms={{
|
|
54
|
+
customValidations: [
|
|
55
|
+
{
|
|
56
|
+
formId: 'signup',
|
|
57
|
+
customValidations: {
|
|
58
|
+
// Return a message to fail, or null to pass.
|
|
59
|
+
email: ({ value }) =>
|
|
60
|
+
typeof value === 'string' && value.endsWith('@example.com')
|
|
61
|
+
? 'example.com addresses are not accepted.'
|
|
62
|
+
: null,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
}}
|
|
63
67
|
>
|
|
64
68
|
<SignupField />
|
|
65
69
|
</EmbeddablesProvider>
|
|
@@ -67,10 +71,17 @@ export function App() {
|
|
|
67
71
|
}
|
|
68
72
|
```
|
|
69
73
|
|
|
70
|
-
- `
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
- `
|
|
74
|
+
- `customValidations` and `serverFormData` go on the provider's `forms` prop.
|
|
75
|
+
- Form schemas come from Core (`config.forms`, populated by `em build`) — they are not
|
|
76
|
+
passed to the provider.
|
|
77
|
+
- A custom validator receives `{ value, values }` and returns a message to fail or
|
|
78
|
+
`null` to pass. `values` is the whole form, so a validator can read sibling fields.
|
|
79
|
+
- When `analytics()` is enabled, form events are tracked automatically — the order of
|
|
80
|
+
`modules` does not matter. Add `'forms'` to `excludeAnalyticsInModules` to turn that off.
|
|
81
|
+
- `useFormField` gives you two ways to write. `setValue` + `onBlur` is commit-on-blur, for
|
|
82
|
+
text inputs: typing updates local state and `form.set()` runs on blur. `commit(value)`
|
|
83
|
+
writes immediately, for controls with no meaningful blur — `select`, checkboxes, radios.
|
|
84
|
+
- `form.set({ field: undefined })` clears one field; `form.clear()` clears the whole form.
|
|
74
85
|
- One live `FormInstance` per `schema.id` is shared across hooks in the same client.
|
|
75
86
|
- Imperative consumers can also call `form.subscribe(listener)` to observe value and error changes.
|
|
76
87
|
|
|
@@ -95,11 +106,11 @@ const schema = {
|
|
|
95
106
|
|
|
96
107
|
const core = initEmbeddables({
|
|
97
108
|
projectId: 'proj_…',
|
|
98
|
-
forms: [],
|
|
109
|
+
forms: { [schema.id]: schema },
|
|
99
110
|
experiments: [],
|
|
100
111
|
})
|
|
101
112
|
|
|
102
|
-
const { getForm } = initForms({ core
|
|
113
|
+
const { getForm } = initForms({ core })
|
|
103
114
|
const form = getForm({ formId: 'signup' })
|
|
104
115
|
|
|
105
116
|
const result = await form.set({ email: 'maria@gmail.com' })
|
|
@@ -119,9 +130,25 @@ Each form is one object: `id`, optional `name`, and `fields`. Supported field
|
|
|
119
130
|
types: `text`, `email`, `number`, `boolean`, `select`, `multiselect`, `json`.
|
|
120
131
|
|
|
121
132
|
Declarative rules: `required`, `minLength`, `maxLength`, `min`, `max`,
|
|
122
|
-
`pattern`, `
|
|
123
|
-
|
|
124
|
-
|
|
133
|
+
`pattern`, `oneOf`, and optional synchronous `validations.custom`.
|
|
134
|
+
|
|
135
|
+
A `select` or `multiselect` field declares its choices in a field-level `options`
|
|
136
|
+
array of `{ value, label?, exclusive? }`, and the field's `type` — not the name of
|
|
137
|
+
a rule — decides whether one or many of them may be selected. An option marked
|
|
138
|
+
`exclusive` on a `multiselect` cannot coexist with any other value: selecting it
|
|
139
|
+
clears the rest, and selecting a regular option afterwards clears it. Those two
|
|
140
|
+
types no longer accept `validations.oneOf`, which stays available on every other
|
|
141
|
+
field type. `em build` migrates it with a warning, carrying across the unique
|
|
142
|
+
non-empty string entries and dropping the rest — an entry that is not a string
|
|
143
|
+
could never have matched a stored value — so a field whose entries are all
|
|
144
|
+
unusable keeps no constraint at all. A schema that reaches `initForms` still
|
|
145
|
+
carrying `oneOf` on either type throws a `SchemaError`.
|
|
146
|
+
|
|
147
|
+
Use string patterns, not `RegExp` objects. Regex flags go in an optional leading
|
|
148
|
+
`(?flags)` prefix inside `pattern` — `(?u)^\p{L}+$` — using any of `dgimsuvy`;
|
|
149
|
+
`g` and `y` are stripped before compilation. For YAML/JSON schemas without inline
|
|
150
|
+
functions, pass validators via `customValidations: [{ formId, customValidations }]`
|
|
151
|
+
on `initForms` / `forms()`.
|
|
125
152
|
|
|
126
153
|
## Form API
|
|
127
154
|
|
|
@@ -150,22 +177,43 @@ backend writes are also best-effort: a failing persistence request never rejects
|
|
|
150
177
|
|
|
151
178
|
| Option | Purpose |
|
|
152
179
|
| ------ | ------- |
|
|
153
|
-
| `core` | Initialized `@embeddables/core` instance (required) |
|
|
154
|
-
| `
|
|
180
|
+
| `core` | Initialized `@embeddables/core` instance with form schemas in `forms` (required) |
|
|
181
|
+
| `customValidations` | Optional `{ formId, customValidations }[]` for schemas that need runtime validators |
|
|
182
|
+
| `serverFormData` | Optional `{ formId, serverFormData }[]`; SSR hydration overrides localStorage on overlapping keys |
|
|
155
183
|
| `analyticsInstance` | Optional analytics client for event tracking only |
|
|
156
184
|
|
|
157
|
-
**`getForm({ formId
|
|
185
|
+
**`getForm({ formId })`**
|
|
158
186
|
|
|
159
187
|
| Argument | Purpose |
|
|
160
188
|
| ------ | ------- |
|
|
161
|
-
| `formId` |
|
|
162
|
-
| `customValidations` | Per-field validators; overrides inline `validations.custom` |
|
|
189
|
+
| `formId` | Id of a schema registered on Core (required); an unknown id throws `FormsError` |
|
|
163
190
|
|
|
164
191
|
`getForm` returns **one instance per form id**: the first call builds it, every
|
|
165
192
|
later call returns that same one, so two handles can never race each other for
|
|
166
|
-
the same storage entry.
|
|
167
|
-
|
|
168
|
-
|
|
193
|
+
the same storage entry. The cache lives on the client, so hold one client per app.
|
|
194
|
+
|
|
195
|
+
## Server (`@embeddables/forms/server`)
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
import { initEmbeddablesServer } from '@embeddables/core/server'
|
|
199
|
+
import { initFormsServer } from '@embeddables/forms/server'
|
|
200
|
+
import { schema as signupSchema } from './embeddables/_dist/forms/<form-id>/schema.js'
|
|
201
|
+
|
|
202
|
+
const server = initEmbeddablesServer({
|
|
203
|
+
projectId: '…',
|
|
204
|
+
forms: { [signupSchema.id]: signupSchema },
|
|
205
|
+
experiments: [],
|
|
206
|
+
})
|
|
207
|
+
const forms = initFormsServer({ server })
|
|
208
|
+
|
|
209
|
+
await forms.getForm({ formId: signupSchema.id }).set({ email: 'maria@gmail.com' })
|
|
210
|
+
const serverFormData = forms.getServerFormData()
|
|
211
|
+
// Pass serverFormData into client initForms({ serverFormData: [{ formId: signupSchema.id, serverFormData: serverFormData[signupSchema.id] }] })
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Server form instances expose `set`, `get`, `getAll`, and
|
|
215
|
+
`getValueByProtocolFieldId` only. Storage is in-memory; nothing is written to
|
|
216
|
+
`localStorage` or durable backends.
|
|
169
217
|
|
|
170
218
|
## Analytics
|
|
171
219
|
|