@picked/client 0.0.64-next.2 → 0.0.64-next.4

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.
Files changed (80) hide show
  1. package/dist/{api-D_oIl-en.js → api-ClqYYoYS.js} +1 -1
  2. package/dist/{api-CuNQH20q.cjs → api-DYgsf40d.cjs} +1 -1
  3. package/dist/cjs/form/index.js +1 -1
  4. package/dist/cjs/index.js +2 -2
  5. package/dist/cjs/plugins/create-experience/index.js +1 -1
  6. package/dist/cjs/plugins/dom-observer/index.js +1 -1
  7. package/dist/cjs/plugins/dynamic-experiences/index.js +1 -1
  8. package/dist/cjs/plugins/experience-browser-storage/index.js +1 -1
  9. package/dist/cjs/plugins/history-browser-storage/index.js +1 -1
  10. package/dist/cjs/plugins/responses-browser-storage/index.js +1 -1
  11. package/dist/cjs/plugins/responses-indexeddb/index.js +1 -1
  12. package/dist/cjs/plugins/single-page-app/index.js +1 -1
  13. package/dist/cjs/plugins/web-components/index.js +1 -1
  14. package/dist/cjs/plugins/window-object/index.js +1 -1
  15. package/dist/cjs/server/index.js +1 -1
  16. package/dist/docs/api/index.md +4 -0
  17. package/dist/docs/content/asset-library.mdx +50 -0
  18. package/dist/docs/content/content-entries.mdx +30 -0
  19. package/dist/docs/content/layouts.mdx +70 -0
  20. package/dist/docs/content/pages.mdx +67 -0
  21. package/dist/docs/content/sections.mdx +33 -0
  22. package/dist/docs/delivery-api/experience-content.md +85 -0
  23. package/dist/docs/delivery-api/index.md +46 -0
  24. package/dist/docs/delivery-api/preview-caching-crawling.md +59 -0
  25. package/dist/docs/delivery-api/project-config.md +56 -0
  26. package/dist/docs/experiences.mdx +3 -1
  27. package/dist/docs/fields.mdx +2 -0
  28. package/dist/docs/guides/build-a-personalized-page.mdx +143 -0
  29. package/dist/docs/sdk/experiences.md +2 -0
  30. package/dist/docs/sdk/index.md +11 -1
  31. package/dist/docs/sdk/ssr/index.md +62 -0
  32. package/dist/docs/sdk/ssr/rendering-and-hydration.md +141 -0
  33. package/dist/docs/sdk/ssr/serialize.md +86 -0
  34. package/dist/docs/sdk/ssr/slots-and-placements.md +35 -0
  35. package/dist/docs/steps.mdx +2 -0
  36. package/dist/{dom-DjfSrkpp.js → dom-BwmTHeT7.js} +1 -1
  37. package/dist/{dom-sqnnO-CT.cjs → dom-CDrHc_j9.cjs} +1 -1
  38. package/dist/esm/form/index.js +1 -1
  39. package/dist/esm/index.js +206 -192
  40. package/dist/esm/plugins/create-experience/index.js +1 -1
  41. package/dist/esm/plugins/dom-observer/index.js +3 -3
  42. package/dist/esm/plugins/dynamic-experiences/index.js +2 -2
  43. package/dist/esm/plugins/experience-browser-storage/index.js +3 -3
  44. package/dist/esm/plugins/history-browser-storage/index.js +2 -2
  45. package/dist/esm/plugins/responses-browser-storage/index.js +2 -2
  46. package/dist/esm/plugins/responses-indexeddb/index.js +1 -1
  47. package/dist/esm/plugins/single-page-app/index.js +2 -2
  48. package/dist/esm/plugins/web-components/index.js +1 -1
  49. package/dist/esm/plugins/window-object/index.js +1 -1
  50. package/dist/esm/server/index.js +33 -33
  51. package/dist/experience-instance-CGzLsO7F.cjs +1 -0
  52. package/dist/{experience-instance-D-VVuZr-.js → experience-instance-RJiegFtc.js} +133 -113
  53. package/dist/id-Js-62re-.cjs +1 -0
  54. package/dist/logger-CSTvAyyB.cjs +1 -0
  55. package/dist/{object-ThO1DEyl.cjs → object-CIkt7Fkr.cjs} +1 -1
  56. package/dist/{plugin-DenaSt69.js → plugin-DlwmZl2C.js} +2 -2
  57. package/dist/{plugin-D-NSmQA5.cjs → plugin-vNgJacw6.cjs} +1 -1
  58. package/dist/{preview-bridge-DIdDvNXQ.cjs → preview-bridge-CpX1BkSA.cjs} +1 -1
  59. package/dist/{preview-bridge-CGyx7dEx.js → preview-bridge-D76l-dPy.js} +2 -2
  60. package/dist/rules-logic-CGZIYgAI.cjs +1 -0
  61. package/dist/{rules-logic-D_CVBuv7.js → rules-logic-HesIUCu6.js} +1 -1
  62. package/dist/{storage-BXSlLTRa.cjs → storage-DUw9UJ2h.cjs} +1 -1
  63. package/dist/{supports-34OzZK5j.cjs → supports-DBzjGaWD.cjs} +1 -1
  64. package/dist/types/entities/condition/condition.d.ts +9 -0
  65. package/dist/types/entities/condition/condition.d.ts.map +1 -1
  66. package/dist/types/lifecycle/condition-selection.d.ts +24 -0
  67. package/dist/types/lifecycle/condition-selection.d.ts.map +1 -1
  68. package/dist/types/lifecycle/rules-logic.d.ts.map +1 -1
  69. package/dist/types/server/index.d.ts +1 -1
  70. package/dist/types/server/index.d.ts.map +1 -1
  71. package/package.json +2 -2
  72. package/dist/experience-instance-CmCEFEJe.cjs +0 -1
  73. package/dist/id-Cg7fVl1O.cjs +0 -1
  74. package/dist/logger-fbvQC40v.cjs +0 -1
  75. package/dist/rules-logic-H6P8xwHF.cjs +0 -1
  76. /package/dist/{id-n2yrHvNo.js → id-Bo4RBeOs.js} +0 -0
  77. /package/dist/{logger-6z_gb4rA.js → logger-DMN6TxJJ.js} +0 -0
  78. /package/dist/{object-DvUDOytC.js → object-BUDGxEqy.js} +0 -0
  79. /package/dist/{storage-hOHCCXvP.js → storage-1MW3-_As.js} +0 -0
  80. /package/dist/{supports-BgjwyijI.js → supports-CO9bJGNl.js} +0 -0
@@ -0,0 +1,141 @@
1
+ ---
2
+ layout: docs
3
+ ---
4
+
5
+ # Rendering & Hydration
6
+
7
+ Seed a Picked client from the server snapshot, render your slots, and hydrate. React and Svelte below.
8
+
9
+ ## React: PickedProvider
10
+
11
+ `PickedProvider` supplies a client to descendant components. Its props are a union of two construction modes:
12
+
13
+ | Props | Mode | Description |
14
+ | --- | --- | --- |
15
+ | `{ options, snapshot? }` | Create | The provider calls `createClient(options)` and hydrates it from the snapshot. |
16
+ | `{ client, snapshot? }` | Adopt | The provider uses an existing client, such as an app-wide global, and optionally seeds it from the snapshot. |
17
+
18
+ The provider seeds synchronously, so the server render and the client's first paint produce the same markup. In the browser it calls `client.start()` from an effect; `start()` is idempotent, so an already-started client is left untouched.
19
+
20
+ ```tsx
21
+ import { PickedProvider, ExperienceSlot } from '@picked/react'
22
+
23
+ <PickedProvider options={options} snapshot={snapshot}>
24
+ <ExperienceSlot name="page" />
25
+ </PickedProvider>
26
+ ```
27
+
28
+ ## Svelte: setPickedContext
29
+
30
+ `setPickedContext` is the Svelte-idiomatic equivalent. Call it once at the top of a component's `<script>`. It takes the same two modes, `{ options, snapshot? }` or `{ client, snapshot? }`, and returns the client.
31
+
32
+ ```svelte
33
+ <script lang="ts">
34
+ import { ExperienceSlot, setPickedContext } from '@picked/svelte'
35
+
36
+ setPickedContext({ options, snapshot })
37
+ </script>
38
+
39
+ <ExperienceSlot name="page" />
40
+ ```
41
+
42
+ ## Placing the slot
43
+
44
+ Drop `<ExperienceSlot name="page" />` where the Page should render. The slot renders every experience that targets it and whose condition is active. See [Slots & Placements](/sdk/ssr/slots-and-placements) for how a slot selects and orders experiences.
45
+
46
+ ## Rendering the page component
47
+
48
+ The SDK does not map content to components. You own the component a Page renders and register it under the name the Page uses, which is the same name the dashboard Page Layout maps to. The component reads the resolved entry data through the content hook: `useStepContent()` in React and `getStepContent()` in Svelte.
49
+
50
+ :::code-group
51
+
52
+ ```tsx [React]
53
+ import { useStepContent } from '@picked/react/hooks'
54
+
55
+ export function CMSPage() {
56
+ const content = useStepContent()
57
+ if (!content) {
58
+ return <p>Loading page content…</p>
59
+ }
60
+
61
+ const title = typeof content.title === 'string' ? content.title : ''
62
+ return (
63
+ <article>
64
+ <h1>{title}</h1>
65
+ </article>
66
+ )
67
+ }
68
+ ```
69
+
70
+ ```svelte [Svelte]
71
+ <script lang="ts">
72
+ import { getStepContent } from '@picked/svelte/hooks'
73
+
74
+ const content = getStepContent()
75
+ </script>
76
+
77
+ {#if $content}
78
+ <article>
79
+ <h1>{typeof $content.title === 'string' ? $content.title : ''}</h1>
80
+ </article>
81
+ {:else}
82
+ <p>Loading page content…</p>
83
+ {/if}
84
+ ```
85
+
86
+ :::
87
+
88
+ `useStepContent()` returns the entry's `data` object directly, or `null` while no content is registered. `getStepContent()` returns a store you read with `$content`.
89
+
90
+ ## Full example
91
+
92
+ This is the verified page-slot wiring. The page client fetches the project config and renders whichever Page experience targets `.picked-slot-page` and matches the current URL.
93
+
94
+ :::code-group
95
+
96
+ ```tsx [React]
97
+ import type { CreateClientOptions } from '@picked/client'
98
+ import type { PickedSnapshot } from '@picked/client/server'
99
+ import { dynamicExperiencesPlugin } from '@picked/client/plugins/dynamic-experiences'
100
+ import { adaptReactComponent, ExperienceSlot, PickedProvider } from '@picked/react'
101
+ import { reactFrameworkPlugin } from '@picked/react/plugins/framework'
102
+ import { CMSPage } from './CMSPage'
103
+
104
+ const options = {
105
+ projectId: 'sdk-cvly-x',
106
+ components: { CMSPage: adaptReactComponent(CMSPage) },
107
+ plugins: [reactFrameworkPlugin({ components: {} }), dynamicExperiencesPlugin()],
108
+ } satisfies CreateClientOptions
109
+
110
+ export function PageSlot({ snapshot }: { snapshot?: PickedSnapshot }) {
111
+ return (
112
+ <PickedProvider options={options} snapshot={snapshot}>
113
+ <ExperienceSlot name="page" />
114
+ </PickedProvider>
115
+ )
116
+ }
117
+ ```
118
+
119
+ ```svelte [Svelte]
120
+ <script lang="ts">
121
+ import type { CreateClientOptions } from '@picked/client'
122
+ import { dynamicExperiencesPlugin } from '@picked/client/plugins/dynamic-experiences'
123
+ import { adaptSvelteComponent, ExperienceSlot, setPickedContext } from '@picked/svelte'
124
+ import { svelteFrameworkPlugin } from '@picked/svelte/plugins/framework'
125
+ import CMSPage from './CMSPage.svelte'
126
+
127
+ const options = {
128
+ projectId: 'sdk-cvly-x',
129
+ components: { CMSPage: adaptSvelteComponent(CMSPage) },
130
+ plugins: [svelteFrameworkPlugin({ components: {} }), dynamicExperiencesPlugin()],
131
+ } satisfies CreateClientOptions
132
+
133
+ setPickedContext({ options })
134
+ </script>
135
+
136
+ <ExperienceSlot name="page" />
137
+ ```
138
+
139
+ :::
140
+
141
+ For the end-to-end version with the server snapshot, see [Build a Personalized Page](/guides/build-a-personalized-page). For the snapshot reference, see [Server Snapshot](/sdk/ssr/serialize). For how experiences register, see [SDK Experiences](/sdk/experiences).
@@ -0,0 +1,86 @@
1
+ ---
2
+ layout: docs
3
+ ---
4
+
5
+ # Server Snapshot
6
+
7
+ `serialize()` evaluates which experiences match a URL and fetches their content on the server, returning a plain-JSON snapshot you pass to the client.
8
+
9
+ ## serialize(client, params)
10
+
11
+ ```ts
12
+ import { createClient } from '@picked/client'
13
+ import { serialize } from '@picked/client/server'
14
+
15
+ const client = createClient(options)
16
+
17
+ const snapshot = await serialize(client, {
18
+ projectConfig,
19
+ attributes: { location: { pathname: '/promo' } },
20
+ })
21
+ ```
22
+
23
+ `serialize()` registers the project config on the client synchronously, evaluates each experience's condition against `attributes.location`, fetches content for the experiences whose steps reference layout content, dedupes the content blobs by id, and returns a `PickedSnapshot`. It expects a per-request client; concurrent calls on a single client are not supported.
24
+
25
+ ```ts
26
+ serialize(client: Picked, params: SerializeParams): Promise<PickedSnapshot>
27
+ ```
28
+
29
+ ## Parameters
30
+
31
+ | Parameter | Type | Default | Description |
32
+ | --- | --- | --- | --- |
33
+ | `projectConfig` | `ProjectConfig` | required | The published or preview config to register and select against. |
34
+ | `attributes.location` | `Partial<SerializeLocation>` | none | URL fields injected into the selection context, such as `pathname`. |
35
+ | `getContent` | `SerializeGetContent` | runtime API | A custom content fetcher used in place of the runtime API. |
36
+ | `previewToken` | `string` | none | Forwarded to the content fetch so preview content resolves during preview SSR. |
37
+ | `timeoutMs` | `number` | `10000` | Per-content-fetch timeout. On timeout the blob is skipped and the render proceeds. |
38
+
39
+ `SerializeLocation` carries the plain URL fields `href`, `pathname`, `search`, `hostname`, `origin`, `protocol`, `port`, `host`, and `hash`. Pass only the ones your conditions read.
40
+
41
+ ## The snapshot
42
+
43
+ ```ts
44
+ interface PickedSnapshot {
45
+ versionId: string | null
46
+ projectConfig: ProjectConfig
47
+ content: ContentBlob[]
48
+ conditionResults: Record<string, boolean>
49
+ }
50
+ ```
51
+
52
+ | Field | Description |
53
+ | --- | --- |
54
+ | `versionId` | The project version the snapshot was selected against, or `null` when unknown (for example on preview). |
55
+ | `projectConfig` | The config the server selected with. It travels so the client renders against the exact same config. |
56
+ | `content` | The deduped content blobs for the selected experiences. |
57
+ | `conditionResults` | The memoized condition results, keyed by rule hash, so the client reuses the server's selection. |
58
+
59
+ The snapshot is plain JSON. Serialize it into your HTML response with `JSON.stringify` and read it back on the client.
60
+
61
+ ## Passing the snapshot to the client
62
+
63
+ Embed the snapshot in your HTML payload, then hand it to the provider (React) or context (Svelte):
64
+
65
+ ```ts
66
+ const html = `
67
+ <div id="root">${appHtml}</div>
68
+ <script>window.__PICKED__ = ${JSON.stringify(snapshot)}</script>
69
+ `
70
+ ```
71
+
72
+ On the client, read `window.__PICKED__` and pass it as the `snapshot` prop. See [Rendering & Hydration](/sdk/ssr/rendering-and-hydration).
73
+
74
+ ## Preview rendering
75
+
76
+ Pass `previewToken` so unpublished content resolves during preview SSR. The server has no access to the request's query string, so the token cannot be recovered any other way:
77
+
78
+ ```ts
79
+ const snapshot = await serialize(client, {
80
+ projectConfig,
81
+ attributes: { location: { pathname: '/promo' } },
82
+ previewToken,
83
+ })
84
+ ```
85
+
86
+ Use the same token when you fetch `projectConfig` from the [Delivery API](/delivery-api), so the config and the content come from the same preview surface.
@@ -0,0 +1,35 @@
1
+ ---
2
+ layout: docs
3
+ ---
4
+
5
+ # Slots & Placements
6
+
7
+ A slot is a named mount point. `<ExperienceSlot name="page" />` renders every experience that targets that slot and whose condition matches.
8
+
9
+ ## What a slot is
10
+
11
+ A slot's `name` maps to the `.picked-slot-<name>` selector. `<ExperienceSlot name="page" />` renders every experience whose target selector is `.picked-slot-page` and whose condition is active. The slot reads each experience's active state from the seeded snapshot, so it renders synchronously during server-side rendering and the hydration first paint.
12
+
13
+ ```tsx
14
+ <ExperienceSlot name="page" />
15
+ ```
16
+
17
+ ## Targeting an experience to a slot
18
+
19
+ Set the experience's target selector to `.picked-slot-<name>`. An experience targeting `.picked-slot-page` renders into `<ExperienceSlot name="page" />`. The slot name and the selector suffix must match.
20
+
21
+ ## Placement order
22
+
23
+ When several experiences target the same slot, they render in placement precedence:
24
+
25
+ ```
26
+ replace -> before -> prepend -> append -> after
27
+ ```
28
+
29
+ `replace` wins. If an active experience uses the `replace` placement, it renders alone and replaces every other match in the slot. Otherwise every active experience renders, in placement order.
30
+
31
+ ## Multiple experiences in one slot
32
+
33
+ The slot collects each experience that targets its selector, drops any whose condition is not active, and sorts the rest by placement. If a `replace` experience is among the active ones, only that experience renders. With no active `replace`, the slot renders all active experiences in `before, prepend, append, after` order.
34
+
35
+ This lets one Page take over a slot with `replace`, while other slots can stack several experiences by using the non-`replace` placements. Conditions decide which experiences are active; see [Conditions](/conditions).
@@ -31,6 +31,8 @@ The key is a unique identifier used in your application. It's set once and can't
31
31
 
32
32
  **Layout Name**: The name of the component layout used to render this step. For example, `card`, `modal`, or `fullscreen`.
33
33
 
34
+ A step can also carry a **Step Layout**, a reusable CMS schema that backs the step with a Content Entry. See [Page & Step Layouts](/content/layouts).
35
+
34
36
  ## Assign fields
35
37
 
36
38
  In the flow builder, drag a field from the entity browser onto a step node. The field appears in the step's **Assigned Fields** list in the inspector.
@@ -1,4 +1,4 @@
1
- import { f as e, i as t, u as n } from "./supports-BgjwyijI.js";
1
+ import { f as e, i as t, u as n } from "./supports-CO9bJGNl.js";
2
2
  //#region src/utils/dom.ts
3
3
  function r(n) {
4
4
  return !e() || !t() ? [] : Array.from(window.document.querySelectorAll(n)).filter((e) => e instanceof HTMLElement);
@@ -1 +1 @@
1
- const e=require(`./supports-34OzZK5j.cjs`);function t(t){return!e.f()||!e.i()?[]:Array.from(window.document.querySelectorAll(t)).filter(e=>e instanceof HTMLElement)}var n=`open`;function r(t){if(!e.u())throw Error(`Shadow DOM is not supported in this environment`);return t.attachShadow({mode:n})}function i(){return new Promise((t,n)=>{if(!e.i()){n();return}document.readyState===`loading`?document.addEventListener(`readystatechange`,()=>t(),{once:!0}):t()})}function a(e,t){let n=new MutationObserver(r=>{document.contains(e)||(n.disconnect(),t())});n.observe(document.body,{childList:!0,subtree:!0})}var o=`picked-root`,s=`picked-target`,c=`picked-root`;function l(e,n){return[...e,{selector:`${c}[experience-id="${n}"]`,placement:`replace`}].flatMap(e=>{let r=t(e.selector).filter(e=>!f(e,n)&&!p(e,n));return{...e,elements:r}})}function u(e,t,n){return e.flatMap(e=>e.elements.map(r=>h(r,e.placement,t,n)))}function d(e,t){e.classList.add(s),e.classList.add(`${s}-${t}`)}function f(e,t){return t?e.classList.contains(`${s}-${t}`):e.classList.contains(`${s}`)}function p(e,t){return t?e.classList.contains(`${o}-${t}`):e.classList.contains(o)}function m(e,t){let n=document.createElement(`div`);return n.style.display=`block`,n.classList.add(o),n.classList.add(`${o}-${e}`),n.classList.add(`${o}-${t}`),n}function h(e,t,n,r){let i,a=m(n,r);if(e instanceof Document)i=e.body.appendChild(a);else{switch(t){case`replace`:e.replaceWith(a);break;case`before`:e.before(a);break;case`after`:e.after(a);break;case`prepend`:e.prepend(a);break;case`append`:e.append(a);break}i=a}return i}Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return p}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return l}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return r}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return a}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return i}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return d}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return u}});
1
+ const e=require("./supports-DBzjGaWD.cjs");function t(t){return!e.f()||!e.i()?[]:Array.from(window.document.querySelectorAll(t)).filter(e=>e instanceof HTMLElement)}var n=`open`;function r(t){if(!e.u())throw Error(`Shadow DOM is not supported in this environment`);return t.attachShadow({mode:n})}function i(){return new Promise((t,n)=>{if(!e.i()){n();return}document.readyState===`loading`?document.addEventListener(`readystatechange`,()=>t(),{once:!0}):t()})}function a(e,t){let n=new MutationObserver(r=>{document.contains(e)||(n.disconnect(),t())});n.observe(document.body,{childList:!0,subtree:!0})}var o=`picked-root`,s=`picked-target`,c=`picked-root`;function l(e,n){return[...e,{selector:`${c}[experience-id="${n}"]`,placement:`replace`}].flatMap(e=>{let r=t(e.selector).filter(e=>!f(e,n)&&!p(e,n));return{...e,elements:r}})}function u(e,t,n){return e.flatMap(e=>e.elements.map(r=>h(r,e.placement,t,n)))}function d(e,t){e.classList.add(s),e.classList.add(`${s}-${t}`)}function f(e,t){return t?e.classList.contains(`${s}-${t}`):e.classList.contains(`${s}`)}function p(e,t){return t?e.classList.contains(`${o}-${t}`):e.classList.contains(o)}function m(e,t){let n=document.createElement(`div`);return n.style.display=`block`,n.classList.add(o),n.classList.add(`${o}-${e}`),n.classList.add(`${o}-${t}`),n}function h(e,t,n,r){let i,a=m(n,r);if(e instanceof Document)i=e.body.appendChild(a);else{switch(t){case`replace`:e.replaceWith(a);break;case`before`:e.before(a);break;case`after`:e.after(a);break;case`prepend`:e.prepend(a);break;case`append`:e.append(a);break}i=a}return i}Object.defineProperty(exports,"a",{enumerable:!0,get:function(){return p}}),Object.defineProperty(exports,"i",{enumerable:!0,get:function(){return l}}),Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return r}}),Object.defineProperty(exports,"o",{enumerable:!0,get:function(){return a}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return i}}),Object.defineProperty(exports,"s",{enumerable:!0,get:function(){return d}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return u}});
@@ -1,4 +1,4 @@
1
- import { t as e } from "../../rules-logic-D_CVBuv7.js";
1
+ import { t as e } from "../../rules-logic-HesIUCu6.js";
2
2
  import { attach as t, combine as n, createEffect as r, createEvent as i, createStore as a, sample as o, split as s } from "effector";
3
3
  //#region src/form/build-responses.ts
4
4
  function c(e, t, n) {