@picked/client 0.0.70-next.6 → 0.0.71-next.1
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/.tsbuildinfo +1 -0
- package/dist/api-CH2FNa6S.cjs +1 -0
- package/dist/api-CgqRvuKL.js +38 -0
- package/dist/cjs/form/index.js +1 -1
- package/dist/cjs/index.js +2 -2
- package/dist/cjs/plugins/dom-observer/index.js +1 -1
- package/dist/cjs/plugins/dynamic-experiences/index.js +1 -1
- package/dist/cjs/server/index.js +1 -1
- package/dist/docs/actions.mdx +38 -18
- package/dist/docs/api/authentication.md +75 -48
- package/dist/docs/api/authorization-and-scopes.md +63 -47
- package/dist/docs/api/error-handling.md +70 -54
- package/dist/docs/api/index.md +41 -22
- package/dist/docs/api/pagination.md +30 -20
- package/dist/docs/api-keys.mdx +11 -6
- package/dist/docs/chrome-extension.md +22 -5
- package/dist/docs/conditions.mdx +32 -19
- package/dist/docs/content/asset-library.mdx +65 -34
- package/dist/docs/content/content-entries.mdx +8 -2
- package/dist/docs/content/layouts.mdx +45 -29
- package/dist/docs/content/pages.mdx +29 -14
- package/dist/docs/content/sections.mdx +19 -8
- package/dist/docs/delivery-api/experience-content.md +10 -5
- package/dist/docs/delivery-api/index.md +19 -12
- package/dist/docs/delivery-api/preview-caching-crawling.md +27 -14
- package/dist/docs/delivery-api/project-config.md +6 -4
- package/dist/docs/experiences.mdx +26 -10
- package/dist/docs/features.mdx +66 -25
- package/dist/docs/fields.mdx +42 -27
- package/dist/docs/flow-builder/building-flows.md +38 -20
- package/dist/docs/flow-builder/condition-builder.md +32 -21
- package/dist/docs/flow-builder/keyboard-shortcuts.md +18 -18
- package/dist/docs/flow-builder/nodes.md +26 -13
- package/dist/docs/flow-builder/overview.mdx +28 -15
- package/dist/docs/gtm-template.md +35 -26
- package/dist/docs/guides/build-a-personalized-page.mdx +36 -18
- package/dist/docs/image-optimisation-service.md +33 -17
- package/dist/docs/index.md +28 -17
- package/dist/docs/mcp.md +46 -38
- package/dist/docs/pickedql/data-sources.md +65 -43
- package/dist/docs/pickedql/dimensions.md +27 -12
- package/dist/docs/pickedql/examples.md +38 -26
- package/dist/docs/pickedql/filtering.md +10 -6
- package/dist/docs/pickedql/index.md +32 -14
- package/dist/docs/pickedql/schema.md +85 -76
- package/dist/docs/pickedql/virtual-functions.md +62 -36
- package/dist/docs/projects.mdx +14 -7
- package/dist/docs/reports.mdx +5 -3
- package/dist/docs/sdk/actions.md +33 -17
- package/dist/docs/sdk/conditions.md +8 -5
- package/dist/docs/sdk/content-security-policy.md +5 -4
- package/dist/docs/sdk/experiences.md +29 -11
- package/dist/docs/sdk/feature-allocation.md +79 -47
- package/dist/docs/sdk/features.md +130 -76
- package/dist/docs/sdk/fields.md +66 -42
- package/dist/docs/sdk/index.md +22 -13
- package/dist/docs/sdk/plugins/create-experience.md +18 -15
- package/dist/docs/sdk/plugins/dynamic-experiences.md +42 -16
- package/dist/docs/sdk/predicates.md +53 -37
- package/dist/docs/sdk/ssr/index.md +25 -8
- package/dist/docs/sdk/ssr/rendering-and-hydration.md +37 -16
- package/dist/docs/sdk/ssr/serialize.md +38 -20
- package/dist/docs/sdk/ssr/slots-and-placements.md +28 -10
- package/dist/docs/sdk/steps.md +100 -55
- package/dist/docs/steps.mdx +20 -11
- package/dist/docs/team-management.md +17 -11
- package/dist/docs/vite-plugin.md +26 -19
- package/dist/docs/workspace-versioning.mdx +14 -8
- package/dist/docs/workspaces.mdx +55 -24
- package/dist/{dom-BwmTHeT7.js → dom-BqcToNL0.js} +1 -3
- package/dist/{dom-CDrHc_j9.cjs → dom-DlLH3FxV.cjs} +1 -1
- package/dist/esm/form/index.js +2 -4
- package/dist/esm/index.js +50 -45
- package/dist/esm/plugins/dom-observer/index.js +1 -1
- package/dist/esm/plugins/dynamic-experiences/index.js +2 -2
- package/dist/esm/preview/index.js +1 -1
- package/dist/esm/server/index.js +12 -10
- package/dist/{experience-instance-DOJShRk5.js → experience-instance-DGk11d8a.js} +3 -3
- package/dist/experience-instance-Dej3P1sE.cjs +1 -0
- package/dist/{plugin-BxMR7fOV.js → plugin-D2UqFikQ.js} +2 -2
- package/dist/plugin-DCW-tnjO.cjs +1 -0
- package/dist/{preview-channel-DW9J8t4B.js → preview-channel-CSr96XAo.js} +1 -1
- package/dist/{protocol-DSqOlW2r.js → protocol-C5KkkiZc.js} +1 -1
- package/dist/types/client.d.ts +7 -6
- package/dist/types/entities/action/action.d.ts +5 -4
- package/dist/types/entities/action/action.d.ts.map +1 -1
- package/dist/types/entities/component/component.d.ts +6 -5
- package/dist/types/entities/component/component.d.ts.map +1 -1
- package/dist/types/entities/condition/condition.d.ts +5 -5
- package/dist/types/entities/content/content.d.ts +4 -3
- package/dist/types/entities/experience/experience.d.ts +11 -10
- package/dist/types/entities/experience/experience.d.ts.map +1 -1
- package/dist/types/entities/experience-instance/experience-instance.d.ts +10 -9
- package/dist/types/entities/feature/feature.d.ts +3 -2
- package/dist/types/entities/feature/feature.d.ts.map +1 -1
- package/dist/types/entities/field/field.d.ts +7 -6
- package/dist/types/entities/framework/framework.d.ts +7 -6
- package/dist/types/entities/framework/framework.d.ts.map +1 -1
- package/dist/types/entities/history/history.d.ts +6 -6
- package/dist/types/entities/plugin/plugin.d.ts +3 -2
- package/dist/types/entities/plugin/plugin.d.ts.map +1 -1
- package/dist/types/entities/predicate/predicate.d.ts +4 -3
- package/dist/types/entities/predicate/predicate.d.ts.map +1 -1
- package/dist/types/entities/response/response-validation.d.ts +3 -2
- package/dist/types/entities/response/response.d.ts +12 -11
- package/dist/types/entities/session/session.d.ts +2 -1
- package/dist/types/entities/step/step.d.ts +6 -5
- package/dist/types/exports.d.ts +2 -1
- package/dist/types/exports.d.ts.map +1 -1
- package/dist/types/form/build-responses.d.ts +2 -1
- package/dist/types/form/create-field-evaluator.d.ts +4 -3
- package/dist/types/form/create-field-validation.d.ts +5 -4
- package/dist/types/form/create-form.d.ts +7 -6
- package/dist/types/form/create-submit-pipeline.d.ts +7 -6
- package/dist/types/form/get-step-fields.d.ts +3 -2
- package/dist/types/lifecycle/condition-selection.d.ts +14 -4
- package/dist/types/lifecycle/lifecycle.d.ts +4 -3
- package/dist/types/lifecycle/rules-logic.d.ts +4 -3
- package/dist/types/lifecycle/state-snapshot.d.ts +4 -3
- package/dist/types/picked.d.ts +64 -62
- package/dist/types/picked.d.ts.map +1 -1
- package/dist/types/ping.d.ts +1 -1
- package/dist/types/ping.d.ts.map +1 -1
- package/dist/types/plugins/create-experience/create-experience.d.ts +9 -8
- package/dist/types/plugins/create-experience/create-experience.d.ts.map +1 -1
- package/dist/types/plugins/create-experience/plugin.d.ts +3 -2
- package/dist/types/plugins/create-experience/plugin.d.ts.map +1 -1
- package/dist/types/plugins/create-experience/serialization.d.ts.map +1 -1
- package/dist/types/plugins/dom-observer/plugin.d.ts +1 -1
- package/dist/types/plugins/dynamic-experiences/plugin.d.ts +3 -3
- package/dist/types/plugins/dynamic-experiences/types.d.ts +8 -5
- package/dist/types/plugins/experience-browser-storage/plugin.d.ts +1 -1
- package/dist/types/plugins/experience-browser-storage/plugin.d.ts.map +1 -1
- package/dist/types/plugins/history-browser-storage/plugin.d.ts +1 -1
- package/dist/types/plugins/history-browser-storage/plugin.d.ts.map +1 -1
- package/dist/types/plugins/responses-browser-storage/plugin.d.ts +1 -1
- package/dist/types/plugins/responses-browser-storage/plugin.d.ts.map +1 -1
- package/dist/types/plugins/responses-indexeddb/plugin.d.ts +1 -1
- package/dist/types/plugins/single-page-app/plugin.d.ts +1 -1
- package/dist/types/plugins/single-page-app/plugin.d.ts.map +1 -1
- package/dist/types/plugins/web-components/plugin.d.ts +2 -1
- package/dist/types/plugins/window-object/plugin.d.ts +2 -2
- package/dist/types/preview-channel.d.ts +5 -1
- package/dist/types/preview-receiver/index.d.ts +18 -1
- package/dist/types/preview-receiver/protocol.d.ts +10 -1
- package/dist/types/server/index.d.ts +3 -3
- package/dist/types/server/serialize.d.ts +2 -2
- package/dist/types/utils/dom.d.ts +2 -1
- package/dist/types/utils/events.d.ts +6 -6
- package/dist/types/utils/events.d.ts.map +1 -1
- package/dist/types/utils/global-obj.d.ts +1 -1
- package/dist/types/utils/global-obj.d.ts.map +1 -1
- package/dist/types/utils/id.d.ts.map +1 -1
- package/dist/types/utils/logger.d.ts +2 -1
- package/dist/types/utils/logger.d.ts.map +1 -1
- package/dist/types/utils/node-tree/get-next-node-from-action.d.ts +2 -1
- package/dist/types/utils/node-tree/get-next-node-from-condition.d.ts +2 -1
- package/dist/types/utils/node-tree/get-next-node.d.ts +1 -1
- package/dist/types/utils/node-tree/get-next-node.d.ts.map +1 -1
- package/dist/types/utils/node-tree/get-origin-node.d.ts +1 -1
- package/dist/types/utils/node-tree/get-origin-node.d.ts.map +1 -1
- package/dist/types/utils/node-tree/min-max-steps-to-leaf.d.ts +1 -1
- package/dist/types/utils/node-tree/min-max-steps-to-leaf.d.ts.map +1 -1
- package/dist/types/utils/node-tree/min-max-steps-to-origin.d.ts +1 -1
- package/dist/types/utils/node-tree/min-max-steps-to-origin.d.ts.map +1 -1
- package/dist/types/utils/node-tree/next-reachable-step-nodes.d.ts +1 -1
- package/dist/types/utils/node-tree/next-reachable-step-nodes.d.ts.map +1 -1
- package/dist/types/utils/nodes.d.ts +1 -1
- package/dist/types/utils/picked-config.d.ts +2 -1
- package/dist/types/utils/rules-logic.d.ts +6 -2
- package/dist/types/utils/storage.d.ts +1 -1
- package/dist/types/utils/storage.d.ts.map +1 -1
- package/package.json +22 -23
- package/dist/api-CoUkIxIr.cjs +0 -1
- package/dist/api-ahdSBQ3Y.js +0 -40
- package/dist/experience-instance-DurC3pE-.cjs +0 -1
- package/dist/plugin-CASdGtTX.cjs +0 -1
|
@@ -4,9 +4,11 @@ layout: docs
|
|
|
4
4
|
|
|
5
5
|
# Server Snapshot
|
|
6
6
|
|
|
7
|
-
`serialize()`
|
|
7
|
+
`serialize()` finds which experiences match a URL and reads their content on the
|
|
8
|
+
server. It returns a snapshot as plain JSON, and you pass that snapshot to the
|
|
9
|
+
client.
|
|
8
10
|
|
|
9
|
-
## serialize(client, params)
|
|
11
|
+
## The `serialize(client, params)` function
|
|
10
12
|
|
|
11
13
|
```ts
|
|
12
14
|
import { createClient } from '@picked/client'
|
|
@@ -20,7 +22,14 @@ const snapshot = await serialize(client, {
|
|
|
20
22
|
})
|
|
21
23
|
```
|
|
22
24
|
|
|
23
|
-
`serialize()` registers the project
|
|
25
|
+
`serialize()` does this. It registers the project options on the client at once. It
|
|
26
|
+
evaluates the condition of each experience against `attributes.location`. It reads
|
|
27
|
+
the content for each experience whose steps point at layout content. It removes the
|
|
28
|
+
duplicate content blobs, which it finds by their IDs. It then returns a
|
|
29
|
+
`PickedSnapshot`.
|
|
30
|
+
|
|
31
|
+
Give `serialize()` one client for each request. It does not support two calls at the
|
|
32
|
+
same time on one client.
|
|
24
33
|
|
|
25
34
|
```ts
|
|
26
35
|
serialize(client: Picked, params: SerializeParams): Promise<PickedSnapshot>
|
|
@@ -30,13 +39,15 @@ serialize(client: Picked, params: SerializeParams): Promise<PickedSnapshot>
|
|
|
30
39
|
|
|
31
40
|
| Parameter | Type | Default | Description |
|
|
32
41
|
| --- | --- | --- | --- |
|
|
33
|
-
| `projectConfig` | `ProjectConfig` | required | The published or preview
|
|
34
|
-
| `attributes.location` | `Partial<SerializeLocation>` | none | URL fields
|
|
35
|
-
| `getContent` | `SerializeGetContent` | runtime API |
|
|
36
|
-
| `previewToken` | `string` | none |
|
|
37
|
-
| `timeoutMs` | `number` | `10000` |
|
|
42
|
+
| `projectConfig` | `ProjectConfig` | required | The published options, or the preview options, to register and choose against. |
|
|
43
|
+
| `attributes.location` | `Partial<SerializeLocation>` | none | The URL fields that go into the selection context, such as `pathname`. |
|
|
44
|
+
| `getContent` | `SerializeGetContent` | runtime API | Your own function to read content, in place of the runtime API. |
|
|
45
|
+
| `previewToken` | `string` | none | The SDK sends this with the content request, so that preview content resolves during a preview render. |
|
|
46
|
+
| `timeoutMs` | `number` | `10000` | The limit for each content request. After the limit, the SDK skips that blob and continues. |
|
|
38
47
|
|
|
39
|
-
`SerializeLocation` carries the
|
|
48
|
+
`SerializeLocation` carries the URL fields `href`, `pathname`, `search`, `hostname`,
|
|
49
|
+
`origin`, `protocol`, `port`, `host`, and `hash`. Pass only the fields that your
|
|
50
|
+
conditions read.
|
|
40
51
|
|
|
41
52
|
## The snapshot
|
|
42
53
|
|
|
@@ -51,16 +62,18 @@ interface PickedSnapshot {
|
|
|
51
62
|
|
|
52
63
|
| Field | Description |
|
|
53
64
|
| --- | --- |
|
|
54
|
-
| `versionId` | The project version the snapshot was
|
|
55
|
-
| `projectConfig` | The
|
|
56
|
-
| `content` | The
|
|
57
|
-
| `conditionResults` | The
|
|
65
|
+
| `versionId` | The project version that the snapshot was chosen against. It is `null` when the version is not known, for example in a preview. |
|
|
66
|
+
| `projectConfig` | The options that the server chose with. They travel to the client, so that the client renders against exactly the same options. |
|
|
67
|
+
| `content` | The content blobs for the chosen experiences, with the duplicates removed. |
|
|
68
|
+
| `conditionResults` | The condition results that the server kept, under a key made from the rule hash. The client then reuses the choice of the server. |
|
|
58
69
|
|
|
59
|
-
The snapshot is plain JSON.
|
|
70
|
+
The snapshot is plain JSON. Write it into your HTML response with `JSON.stringify`
|
|
71
|
+
and read it back on the client.
|
|
60
72
|
|
|
61
|
-
##
|
|
73
|
+
## Pass the snapshot to the client
|
|
62
74
|
|
|
63
|
-
Embed the snapshot in your HTML payload
|
|
75
|
+
Embed the snapshot in your HTML payload. Then give it to the provider in React, or to
|
|
76
|
+
the context in Svelte:
|
|
64
77
|
|
|
65
78
|
```ts
|
|
66
79
|
const html = `
|
|
@@ -69,11 +82,14 @@ const html = `
|
|
|
69
82
|
`
|
|
70
83
|
```
|
|
71
84
|
|
|
72
|
-
On the client, read `window.__PICKED__` and pass it as the `snapshot` prop. See
|
|
85
|
+
On the client, read `window.__PICKED__` and pass it as the `snapshot` prop. See
|
|
86
|
+
[Rendering and Hydration](/sdk/ssr/rendering-and-hydration).
|
|
73
87
|
|
|
74
|
-
##
|
|
88
|
+
## Render a preview
|
|
75
89
|
|
|
76
|
-
Pass `previewToken
|
|
90
|
+
Pass `previewToken`, so that unpublished content resolves during a preview render.
|
|
91
|
+
The server cannot read the query string of the request, so there is no other way to
|
|
92
|
+
give it the token:
|
|
77
93
|
|
|
78
94
|
```ts
|
|
79
95
|
const snapshot = await serialize(client, {
|
|
@@ -83,4 +99,6 @@ const snapshot = await serialize(client, {
|
|
|
83
99
|
})
|
|
84
100
|
```
|
|
85
101
|
|
|
86
|
-
Use the same token when you
|
|
102
|
+
Use the same token when you read `projectConfig` from the
|
|
103
|
+
[Delivery API](/delivery-api). The options and the content then come from the same
|
|
104
|
+
preview.
|
|
@@ -2,34 +2,52 @@
|
|
|
2
2
|
layout: docs
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
-
# Slots
|
|
5
|
+
# Slots and Placements
|
|
6
6
|
|
|
7
|
-
A slot is a named mount point. `<ExperienceSlot name="page" />` renders every
|
|
7
|
+
A slot is a named mount point. `<ExperienceSlot name="page" />` renders every
|
|
8
|
+
experience that points at that slot and whose condition matches.
|
|
8
9
|
|
|
9
10
|
## What a slot is
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
The `name` of a slot points at the `.picked-slot-<name>` selector. So
|
|
13
|
+
`<ExperienceSlot name="page" />` renders every experience with the target selector
|
|
14
|
+
`.picked-slot-page`, when the condition of that experience is active.
|
|
15
|
+
|
|
16
|
+
The slot reads the active state of each experience from the seeded snapshot. It
|
|
17
|
+
therefore renders at once, both in server-side rendering and at the first paint of
|
|
18
|
+
the hydration.
|
|
12
19
|
|
|
13
20
|
```tsx
|
|
14
21
|
<ExperienceSlot name="page" />
|
|
15
22
|
```
|
|
16
23
|
|
|
17
|
-
##
|
|
24
|
+
## Point an experience at a slot
|
|
18
25
|
|
|
19
|
-
Set the
|
|
26
|
+
Set the target selector of the experience to `.picked-slot-<name>`. An experience
|
|
27
|
+
that points at `.picked-slot-page` renders into `<ExperienceSlot name="page" />`. The
|
|
28
|
+
name of the slot and the end of the selector must match.
|
|
20
29
|
|
|
21
30
|
## Placement order
|
|
22
31
|
|
|
23
|
-
When
|
|
32
|
+
When more than one experience points at the same slot, they render in this placement
|
|
33
|
+
order:
|
|
24
34
|
|
|
25
35
|
```
|
|
26
36
|
replace -> before -> prepend -> append -> after
|
|
27
37
|
```
|
|
28
38
|
|
|
29
|
-
`replace` wins.
|
|
39
|
+
`replace` wins. An active experience with the `replace` placement renders alone, and
|
|
40
|
+
it replaces every other match in the slot. In every other case each active experience
|
|
41
|
+
renders, in placement order.
|
|
30
42
|
|
|
31
|
-
##
|
|
43
|
+
## More than one experience in one slot
|
|
32
44
|
|
|
33
|
-
The slot collects each experience that
|
|
45
|
+
The slot collects each experience that points at its selector. It drops each one
|
|
46
|
+
whose condition is not active. It then sorts the rest into placement order. When a `replace`
|
|
47
|
+
experience is among the active ones, only that experience renders. With no active
|
|
48
|
+
`replace`, the slot renders every active experience in the order `before`, `prepend`,
|
|
49
|
+
`append`, `after`.
|
|
34
50
|
|
|
35
|
-
|
|
51
|
+
One Page can therefore take a whole slot with `replace`, while another slot holds
|
|
52
|
+
several experiences that use the other placements. A condition decides which
|
|
53
|
+
experiences are active. See [Conditions](/conditions).
|
package/dist/docs/sdk/steps.md
CHANGED
|
@@ -4,23 +4,33 @@ layout: docs
|
|
|
4
4
|
|
|
5
5
|
# Steps
|
|
6
6
|
|
|
7
|
-
Render steps with `<StepRouter>` and `<Step>` from `@picked/react` or
|
|
7
|
+
Render your steps with `<StepRouter>` and `<Step>` from `@picked/react` or
|
|
8
|
+
`@picked/svelte`. Reach the active step through `useStep()` or `getStep()`.
|
|
8
9
|
|
|
9
10
|
## How steps work
|
|
10
11
|
|
|
11
|
-
When an experience starts, its steps
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
When an experience starts, its steps form a directed graph of nodes. The SDK walks
|
|
13
|
+
this graph to find which step to show:
|
|
14
|
+
|
|
15
|
+
1. **Initialization**: when the experience opens, the SDK starts at the origin node.
|
|
16
|
+
It walks the graph to find the first step.
|
|
17
|
+
2. **Step entry**: the SDK sets the active step. It sends a `step_enter` event on the
|
|
18
|
+
event bus.
|
|
19
|
+
3. **Field evaluation**: the SDK evaluates the fields of the step. It shows or hides
|
|
20
|
+
a field with a condition, and the current state decides which.
|
|
21
|
+
4. **User input**: the user fills in the fields of the step. Validation runs in the
|
|
22
|
+
browser on blur and on submit.
|
|
23
|
+
5. **Step submission**: when the user submits, a `step_submit` event fires. The SDK
|
|
24
|
+
commits the field responses to the client state.
|
|
25
|
+
6. **Navigation**: the SDK follows the edge from the current step node to the next
|
|
26
|
+
one. It walks through each condition and action on the way.
|
|
27
|
+
7. **Completion**: when no step remains, the experience flow is complete.
|
|
20
28
|
|
|
21
29
|
## Step routing
|
|
22
30
|
|
|
23
|
-
`StepRouter`
|
|
31
|
+
`StepRouter` watches the current step state of the experience. It activates the
|
|
32
|
+
`Step` whose `id` matches. Each `Step` registers itself on mount and unregisters on
|
|
33
|
+
unmount, so a tab or a drawer that renders lazily works with no extra code.
|
|
24
34
|
|
|
25
35
|
:::code-group
|
|
26
36
|
|
|
@@ -69,12 +79,12 @@ function MyExperience() {
|
|
|
69
79
|
:::
|
|
70
80
|
|
|
71
81
|
:::note
|
|
72
|
-
|
|
82
|
+
A router must hold every Step, even when there is only one Step.
|
|
73
83
|
:::
|
|
74
84
|
|
|
75
85
|
### Lazy-loaded steps
|
|
76
86
|
|
|
77
|
-
For large
|
|
87
|
+
For a large experience, load a step lazily with a dynamic import:
|
|
78
88
|
|
|
79
89
|
:::code-group
|
|
80
90
|
|
|
@@ -98,11 +108,13 @@ const WelcomeScreen = lazy(() => import('./WelcomeScreen'))
|
|
|
98
108
|
|
|
99
109
|
:::
|
|
100
110
|
|
|
101
|
-
The `component` prop
|
|
111
|
+
The `component` prop takes a `Promise<Component>`. The SDK does not load the content
|
|
112
|
+
of the step until that step becomes active.
|
|
102
113
|
|
|
103
|
-
##
|
|
114
|
+
## Reach the step context
|
|
104
115
|
|
|
105
|
-
Inside a `Step` component
|
|
116
|
+
Inside a `Step` component, or inside any component that a `Step` renders, call
|
|
117
|
+
`getStep()` or `useStep()` to reach the reactive context of the step:
|
|
106
118
|
|
|
107
119
|
:::code-group
|
|
108
120
|
|
|
@@ -161,24 +173,29 @@ function ContactForm() {
|
|
|
161
173
|
|
|
162
174
|
:::
|
|
163
175
|
|
|
164
|
-
`getStep()`
|
|
176
|
+
`getStep()` in Svelte, and `useStep()` in React, return a `StepContext` with these
|
|
177
|
+
properties:
|
|
165
178
|
|
|
166
179
|
| Property | Type | Description |
|
|
167
180
|
| ---------- | --------------------------------------------- | ------------------------------------------------------------------------------ |
|
|
168
|
-
| `step` | `Store<ExperienceStep>` |
|
|
169
|
-
| `fields` | `Store<FormField[]>` |
|
|
170
|
-
| `isActive` | `Store<boolean>` (Svelte) / `boolean` (React) | Whether this step
|
|
171
|
-
| `form` | Object |
|
|
181
|
+
| `step` | `Store<ExperienceStep>` | A reactive store that holds the `id` and the `label` of the step |
|
|
182
|
+
| `fields` | `Store<FormField[]>` | A reactive store of the fields, with their current values, errors, and handlers |
|
|
183
|
+
| `isActive` | `Store<boolean>` (Svelte) / `boolean` (React) | Whether the router holds this step as the active one |
|
|
184
|
+
| `form` | Object | The handlers for a form submission. See [Forms](#forms). |
|
|
172
185
|
|
|
173
186
|
## Forms
|
|
174
187
|
|
|
175
|
-
When a step has fields, `getStep()`
|
|
188
|
+
When a step has fields, `getStep()` and `useStep()` give you a `form` object. Use it
|
|
189
|
+
for the submission, the validation, and the move to the next step.
|
|
176
190
|
|
|
177
|
-
Both
|
|
191
|
+
Both take an optional object of callbacks, as in `getStep({ onSuccess })` or
|
|
192
|
+
`useStep({ onSuccess })`. This is the recommended way to act on a submission. The
|
|
193
|
+
slots are `onBeforeSubmit`, `onSuccess`, and `onError`. This page covers each one
|
|
194
|
+
below.
|
|
178
195
|
|
|
179
|
-
###
|
|
196
|
+
### Handle a submission
|
|
180
197
|
|
|
181
|
-
Bind `form.props` to your `<form>` element
|
|
198
|
+
Bind `form.props` to the `onsubmit` handler of your `<form>` element:
|
|
182
199
|
|
|
183
200
|
:::code-group
|
|
184
201
|
|
|
@@ -242,19 +259,22 @@ function StepForm() {
|
|
|
242
259
|
|
|
243
260
|
:::
|
|
244
261
|
|
|
245
|
-
|
|
262
|
+
When you submit the form, the SDK does this:
|
|
246
263
|
|
|
247
|
-
1.
|
|
248
|
-
2.
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
264
|
+
1. It collects the field values from the HTML form.
|
|
265
|
+
2. It runs each `onBeforeSubmit` callback, one after another. A throw here aborts the
|
|
266
|
+
submit and commits nothing.
|
|
267
|
+
3. It checks each required field and each validation rule.
|
|
268
|
+
4. It commits the responses to the experience state.
|
|
269
|
+
5. It fires `form_submit_success` or `form_submit_error` on the event bus.
|
|
270
|
+
6. It runs your `onSuccess` or `onError` callback.
|
|
271
|
+
7. It moves to the next step. A call to `stopNavigation` stops this. A callback that
|
|
272
|
+
failed in step 6 does not stop it.
|
|
254
273
|
|
|
255
|
-
###
|
|
274
|
+
### Transform data before a submission
|
|
256
275
|
|
|
257
|
-
Use `onBeforeSubmit` to intercept and transform field data before
|
|
276
|
+
Use `onBeforeSubmit` to intercept and transform the field data before the SDK stores it. The callbacks run one after another. Each one takes the output of the one before
|
|
277
|
+
it:
|
|
258
278
|
|
|
259
279
|
:::code-group
|
|
260
280
|
|
|
@@ -264,7 +284,7 @@ Use `onBeforeSubmit` to intercept and transform field data before it is committe
|
|
|
264
284
|
|
|
265
285
|
const { form } = getStep({
|
|
266
286
|
onBeforeSubmit: async (data) => {
|
|
267
|
-
// Normalize email to lowercase
|
|
287
|
+
// Normalize the email address to lowercase first.
|
|
268
288
|
return {
|
|
269
289
|
...data,
|
|
270
290
|
'email-address': String(data['email-address']).toLowerCase(),
|
|
@@ -282,7 +302,7 @@ import { useStep } from '@picked/react/hooks'
|
|
|
282
302
|
function ContactStep() {
|
|
283
303
|
const { form } = useStep({
|
|
284
304
|
onBeforeSubmit: async (data) => {
|
|
285
|
-
// Normalize email to lowercase
|
|
305
|
+
// Normalize the email address to lowercase first.
|
|
286
306
|
return {
|
|
287
307
|
...data,
|
|
288
308
|
'email-address': String(data['email-address']).toLowerCase(),
|
|
@@ -297,12 +317,16 @@ function ContactStep() {
|
|
|
297
317
|
:::
|
|
298
318
|
|
|
299
319
|
:::warning
|
|
300
|
-
|
|
320
|
+
When an `onBeforeSubmit` callback throws, the SDK aborts the submit before it commits
|
|
321
|
+
anything, and the step does not advance. This is deliberate. A payload that is only
|
|
322
|
+
part transformed would store data that looks correct but is wrong. Use
|
|
323
|
+
`onBeforeSubmit` for a transform and for a validation check. Put a side effect, such as an
|
|
324
|
+
analytics call or a backend call, in `onSuccess`.
|
|
301
325
|
:::
|
|
302
326
|
|
|
303
327
|
### Success and error callbacks
|
|
304
328
|
|
|
305
|
-
Pass `onSuccess` and `onError` to
|
|
329
|
+
Pass `onSuccess` and `onError` to act on the outcome of a submission:
|
|
306
330
|
|
|
307
331
|
:::code-group
|
|
308
332
|
|
|
@@ -342,13 +366,20 @@ function ContactStep() {
|
|
|
342
366
|
|
|
343
367
|
:::
|
|
344
368
|
|
|
345
|
-
|
|
369
|
+
A callback belongs to the component that passes it. The SDK releases the callback
|
|
370
|
+
when that component unmounts, so you write no teardown code. A new inline function on
|
|
371
|
+
a re-render replaces the earlier one and does not add a second one. One submit
|
|
372
|
+
therefore runs each callback exactly one time.
|
|
346
373
|
|
|
347
|
-
|
|
374
|
+
An `onSuccess` or `onError` callback that throws or rejects cannot stop the flow. The
|
|
375
|
+
step still advances, and the SDK reports the failure on the client event bus as
|
|
376
|
+
`form_submit_callback_error`, so the failure stays observable.
|
|
348
377
|
|
|
349
|
-
####
|
|
378
|
+
#### Register a callback in code
|
|
350
379
|
|
|
351
|
-
`form.onSuccess`, `form.onError
|
|
380
|
+
`form.onSuccess`, `form.onError`, and `form.onBeforeSubmit` are an escape hatch, for
|
|
381
|
+
a callback that you must attach only when a condition holds. They return nothing. The
|
|
382
|
+
SDK releases them for you, as above.
|
|
352
383
|
|
|
353
384
|
```tsx [React]
|
|
354
385
|
function ContactStep({ trackConversions }: { trackConversions: boolean }) {
|
|
@@ -363,12 +394,17 @@ function ContactStep({ trackConversions }: { trackConversions: boolean }) {
|
|
|
363
394
|
```
|
|
364
395
|
|
|
365
396
|
:::warning
|
|
366
|
-
Register from the component
|
|
397
|
+
Register from the body of the component, and not from an event handler. A
|
|
398
|
+
registration inside a click handler is added each time the handler runs, and the SDK
|
|
399
|
+
releases it only when the component unmounts. The registrations therefore accumulate.
|
|
400
|
+
Use the options object above where you can.
|
|
367
401
|
:::
|
|
368
402
|
|
|
369
|
-
###
|
|
403
|
+
### Stop the move to the next step
|
|
370
404
|
|
|
371
|
-
By default
|
|
405
|
+
By default the experience advances to the next step after a successful submission.
|
|
406
|
+
Call `stopNavigation()` inside `onSuccess` to stop this. Do it when you must choose
|
|
407
|
+
the next step yourself:
|
|
372
408
|
|
|
373
409
|
:::code-group
|
|
374
410
|
|
|
@@ -383,7 +419,7 @@ By default, the experience advances to the next step after a successful submissi
|
|
|
383
419
|
onSuccess: ({ data, stopNavigation }) => {
|
|
384
420
|
stopNavigation()
|
|
385
421
|
|
|
386
|
-
//
|
|
422
|
+
// Choose the next step from the data that the user sent.
|
|
387
423
|
if (data['plan-type'] === 'enterprise') {
|
|
388
424
|
experience.goToStepById('enterprise-contact')
|
|
389
425
|
} else {
|
|
@@ -407,7 +443,7 @@ function PlanStep() {
|
|
|
407
443
|
onSuccess: ({ data, stopNavigation }) => {
|
|
408
444
|
stopNavigation()
|
|
409
445
|
|
|
410
|
-
//
|
|
446
|
+
// Choose the next step from the data that the user sent.
|
|
411
447
|
if (data['plan-type'] === 'enterprise') {
|
|
412
448
|
experience.goToStepById('enterprise-contact')
|
|
413
449
|
} else {
|
|
@@ -422,13 +458,22 @@ function PlanStep() {
|
|
|
422
458
|
|
|
423
459
|
:::
|
|
424
460
|
|
|
425
|
-
|
|
461
|
+
Move by step `id` with `goToStepById('<step-id>')`, or by the `key` of the step with
|
|
462
|
+
`goToStepByKey('<step-key>')`. Both jump directly to the step node that matches. When
|
|
463
|
+
no step matches, the call does nothing.
|
|
426
464
|
|
|
427
465
|
## Best practices
|
|
428
466
|
|
|
429
|
-
- **Give
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
- **
|
|
433
|
-
|
|
434
|
-
- **
|
|
467
|
+
- **Give each step a meaningful label**: analytics events and the dashboard both show
|
|
468
|
+
the `label`. Use a name that says what the step does, such as `"Contact Details"`
|
|
469
|
+
and not `"Step 2"`.
|
|
470
|
+
- **Keep each step small**: each step must collect one small set of related
|
|
471
|
+
information. More steps with fewer fields each reduce the number of users who leave.
|
|
472
|
+
- **Use a condition to take a branch**: do not make a second copy of a step. Write the
|
|
473
|
+
branch with `nextConditional()`, so that you can reuse a shared step.
|
|
474
|
+
- **Use `stopNavigation` only when you must**: an automatic move after a submit is
|
|
475
|
+
what a user expects. Stop it only when you must choose the next step yourself.
|
|
476
|
+
- **Check a field on blur, not on change**: the SDK does this for you. A field shows
|
|
477
|
+
an error after the user leaves it, and not while the user types.
|
|
478
|
+
- **Load a large step component lazily**: use the `component` prop on `<Step>` with a
|
|
479
|
+
dynamic import, for content that only some users see.
|
package/dist/docs/steps.mdx
CHANGED
|
@@ -6,7 +6,8 @@ import { Screenshot } from '../../components/Screenshot'
|
|
|
6
6
|
|
|
7
7
|
# Steps
|
|
8
8
|
|
|
9
|
-
A step is
|
|
9
|
+
A step is one screen or card that a visitor sees during an experience. Each step
|
|
10
|
+
holds one or more fields. A field collects data or shows content.
|
|
10
11
|
|
|
11
12
|
<Screenshot
|
|
12
13
|
route="/{orgSlug}/{projectId}/workspaces/{workspaceId}/steps"
|
|
@@ -18,27 +19,35 @@ A step is a visual screen or card shown to visitors during an experience. Each s
|
|
|
18
19
|
1. Open your project.
|
|
19
20
|
2. Click **Steps** in the sidebar.
|
|
20
21
|
3. Click **New Step**.
|
|
21
|
-
4. Enter a name and key.
|
|
22
|
+
4. Enter a name and a key.
|
|
22
23
|
5. Click **Create**.
|
|
23
24
|
|
|
24
|
-
The key is a unique identifier
|
|
25
|
+
The key is a unique identifier for your application. You set it once, and you cannot
|
|
26
|
+
change it later.
|
|
25
27
|
|
|
26
|
-
##
|
|
28
|
+
## Set up a step
|
|
27
29
|
|
|
28
|
-
**Name**: The
|
|
30
|
+
**Name**: The name that the dashboard shows. Your visitors do not see it.
|
|
29
31
|
|
|
30
|
-
**Key**: The
|
|
32
|
+
**Key**: The identifier that your application uses for this step. Your
|
|
33
|
+
visitors do not see it.
|
|
31
34
|
|
|
32
|
-
**Layout Name**: The name of the component layout
|
|
35
|
+
**Layout Name**: The name of the component layout that renders this step. Examples
|
|
36
|
+
are `card`, `modal`, and `fullscreen`.
|
|
33
37
|
|
|
34
|
-
A step can also carry a **Step Layout
|
|
38
|
+
A step can also carry a **Step Layout**. A step layout is a reusable CMS schema, and
|
|
39
|
+
it gives the step a Content Entry. See [Page and Step Layouts](/content/layouts).
|
|
35
40
|
|
|
36
41
|
## Assign fields
|
|
37
42
|
|
|
38
|
-
In the flow builder, drag a field from the entity browser onto a step node. The field
|
|
43
|
+
In the flow builder, drag a field from the entity browser onto a step node. The field
|
|
44
|
+
appears in the **Assigned Fields** list of the step, in the inspector.
|
|
39
45
|
|
|
40
|
-
You can also
|
|
46
|
+
You can also choose the step node and expand the **Assigned Fields** section to see
|
|
47
|
+
and control the assigned fields. Click a field to open its inspector.
|
|
41
48
|
|
|
42
49
|
## Reuse steps
|
|
43
50
|
|
|
44
|
-
|
|
51
|
+
A step is a project-level entity. You can drag the same step into more than one
|
|
52
|
+
experience flow. A change to the options of a step applies at every place that uses
|
|
53
|
+
it.
|
|
@@ -4,13 +4,16 @@ layout: docs
|
|
|
4
4
|
|
|
5
5
|
# Team management
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Control who can reach your organization and what each person can do. An organization
|
|
8
|
+
is the top-level container for your projects and members.
|
|
8
9
|
|
|
9
10
|
## Organizations
|
|
10
11
|
|
|
11
|
-
An organization groups your projects, members, and settings. You can belong
|
|
12
|
+
An organization groups your projects, your members, and your settings. You can belong
|
|
13
|
+
to more than one organization.
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
To move between organizations, use the organization control at the top of the
|
|
16
|
+
sidebar.
|
|
14
17
|
|
|
15
18
|
## Invite members
|
|
16
19
|
|
|
@@ -18,23 +21,26 @@ Switch between organizations using the organization selector in the top of the s
|
|
|
18
21
|
2. Click **Members**.
|
|
19
22
|
3. Click **Invite**.
|
|
20
23
|
4. Enter an email address.
|
|
21
|
-
5.
|
|
24
|
+
5. Choose a role.
|
|
22
25
|
6. Click **Send Invite**.
|
|
23
26
|
|
|
24
|
-
The
|
|
27
|
+
The person that you invite receives an email with a link to join your organization.
|
|
28
|
+
A person without a Picked account must create one first.
|
|
25
29
|
|
|
26
30
|
## Remove members
|
|
27
31
|
|
|
28
32
|
1. Open your organization.
|
|
29
33
|
2. Click **Members**.
|
|
30
|
-
3. Find the member you want to remove.
|
|
31
|
-
4. Click **Remove** next to
|
|
32
|
-
5. Confirm
|
|
34
|
+
3. Find the member that you want to remove.
|
|
35
|
+
4. Click **Remove** next to the name of the member.
|
|
36
|
+
5. Confirm.
|
|
33
37
|
|
|
34
|
-
The member
|
|
38
|
+
The member loses access to every project in the organization immediately.
|
|
35
39
|
|
|
36
40
|
## Roles and permissions
|
|
37
41
|
|
|
38
|
-
|
|
42
|
+
Each member holds a role, and the role controls what the member can reach. A role
|
|
43
|
+
decides whether a member can create a project, edit an experience, control other
|
|
44
|
+
members, or read a report.
|
|
39
45
|
|
|
40
|
-
|
|
46
|
+
Give each member the smallest role that the work of that member needs.
|