@picked/react 0.0.69 → 0.0.70-next.2
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/experience/HtmlRender.d.ts +9 -0
- package/dist/experience/HtmlRender.d.ts.map +1 -1
- package/dist/experience/HtmlRender.js +1 -1
- package/dist/experience/morph.d.ts +26 -0
- package/dist/experience/morph.d.ts.map +1 -0
- package/dist/experience/morph.js +1 -0
- package/dist/form/index.d.ts +19 -8
- package/dist/form/index.d.ts.map +1 -1
- package/dist/form/index.js +1 -1
- package/dist/form/use-form-callbacks.d.ts +45 -0
- package/dist/form/use-form-callbacks.d.ts.map +1 -0
- package/dist/form/use-form-callbacks.js +1 -0
- package/dist/hooks/step.d.ts +20 -4
- package/dist/hooks/step.d.ts.map +1 -1
- package/dist/hooks/step.js +1 -1
- package/package.json +2 -2
|
@@ -13,6 +13,15 @@ interface HtmlRenderProps {
|
|
|
13
13
|
* client-only mount or a later content change attaches and writes the shadow root imperatively
|
|
14
14
|
* instead. A non-matching value or a `null` content renders nothing.
|
|
15
15
|
*
|
|
16
|
+
* Inside a Picked live-preview session the update path changes: content arrives at typing speed, so
|
|
17
|
+
* after the first render the shadow root is **morphed** rather than rewritten, and page scripts are
|
|
18
|
+
* **frozen** — nothing re-executes. The two are one behaviour and must never be separated. Freezing
|
|
19
|
+
* is what stops a customer's analytics and third-party pixels firing once per keystroke from the
|
|
20
|
+
* dashboard; morphing is what keeps the page alive without them, since a wholesale rewrite would
|
|
21
|
+
* detach every listener with nothing left to re-bind it. Markup inserted during a preview morph has
|
|
22
|
+
* its scripts neutralised, so a newly-added section renders but has no behaviour until the next full
|
|
23
|
+
* load — reported to the embedder so the editor can say so.
|
|
24
|
+
*
|
|
16
25
|
* @param {HtmlRenderProps} props - The component props.
|
|
17
26
|
* @returns {React.JSX.Element} The shadow-root host element.
|
|
18
27
|
* @example
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HtmlRender.d.ts","sourceRoot":"","sources":["../../src/experience/HtmlRender.tsx"],"names":[],"mappings":"AAAA,8GAA8G;AAC9G,OAAO,KAA4B,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"HtmlRender.d.ts","sourceRoot":"","sources":["../../src/experience/HtmlRender.tsx"],"names":[],"mappings":"AAAA,8GAA8G;AAC9G,OAAO,KAA4B,MAAM,OAAO,CAAA;AAWhD,UAAU,eAAe;IACvB,wEAAwE;IACxE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CACvB;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,iBAAS,UAAU,CAAC,EAAE,IAAI,EAAE,EAAE,eAAe,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAuChE;AAED,eAAe,UAAU,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useEffect as
|
|
1
|
+
import{morphInto as e}from"./morph.js";import{useEffect as t,useRef as n}from"react";import{isHtmlAssetValue as r,isPreviewSession as i,rehydrateScripts as a,reportInertScriptSubtrees as o,supportsShadowRoot as s,supportsWindow as c}from"@picked/client";import{jsx as l}from"react/jsx-runtime";function u({html:u}){let d=(r(u)?u:null)?.content??``,f=n(null),p=n(!1);return t(()=>{let t=f.current;if(!s()||!t)return;let n=t.shadowRoot,r=!p.current;if(p.current=!0,n&&r){d&&a(n);return}let c=n??t.attachShadow({mode:`open`});if(!r&&i()){let{inertScriptSubtrees:t}=e(c,d);o(t);return}c.innerHTML=d,d&&a(c)},[d]),c()?l(`div`,{ref:f,suppressHydrationWarning:!0}):l(`div`,{ref:f,suppressHydrationWarning:!0,dangerouslySetInnerHTML:{__html:`<template shadowrootmode="open">${d}</template>`}})}export{u as default};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Outcome of a morph pass. Returned by {@link morphInto}. */
|
|
2
|
+
interface MorphResult {
|
|
3
|
+
/**
|
|
4
|
+
* How many inserted subtrees contained at least one `<script>`. Those scripts are neutralised
|
|
5
|
+
* before insertion, so the markup renders but has no behaviour until the next full load. The
|
|
6
|
+
* dashboard surfaces this count so an author who adds a section understands why its carousel is
|
|
7
|
+
* inert, instead of filing it as a bug.
|
|
8
|
+
*/
|
|
9
|
+
inertScriptSubtrees: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Morphs a root's children to match `markup`, preserving every node the new markup did not change.
|
|
13
|
+
*
|
|
14
|
+
* Scripts are never executed: existing ones are left untouched (mutating a script's text does not
|
|
15
|
+
* re-run it) and inserted ones are neutralised before they reach the document. The returned count
|
|
16
|
+
* reports how many inserted subtrees were affected, so the editor can say so.
|
|
17
|
+
*
|
|
18
|
+
* @param {ParentNode & Node} root - The live root — typically a shadow root — to update.
|
|
19
|
+
* @param {string} markup - The desired markup.
|
|
20
|
+
* @returns {MorphResult} How many inserted subtrees had their scripts neutralised.
|
|
21
|
+
* @example
|
|
22
|
+
* const { inertScriptSubtrees } = morphInto(shadowRoot, '<h1>Hello</h1>')
|
|
23
|
+
*/
|
|
24
|
+
export declare function morphInto(root: ParentNode & Node, markup: string): MorphResult;
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=morph.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"morph.d.ts","sourceRoot":"","sources":["../../src/experience/morph.ts"],"names":[],"mappings":"AAmBA,8DAA8D;AAC9D,UAAU,WAAW;IACnB;;;;;OAKG;IACH,mBAAmB,EAAE,MAAM,CAAA;CAC5B;AAuHD;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,WAAW,CAO9E"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{PICKED_SCRIPT_TYPE as e}from"@picked/client";var t=`data-picked-hydrated`;function n(t){let n=t.querySelectorAll(`script`),r=t.tagName===`SCRIPT`;if(n.length===0&&!r)return!1;r&&t.setAttribute(`type`,e);for(let t of Array.from(n))t.setAttribute(`type`,e);return!0}function r(e,t,r){let i=t.importNode(e,!0);return i instanceof Element&&n(i)&&(r.inertScriptSubtrees+=1),i}function i(e,n){for(let t of Array.from(n.attributes))e.getAttribute(t.name)!==t.value&&e.setAttribute(t.name,t.value);for(let r of Array.from(e.attributes))r.name!==t&&(n.hasAttribute(r.name)||e.removeAttribute(r.name))}function a(e,t){return e.nodeType===t.nodeType&&e.nodeName===t.nodeName}function o(e,t,n,s){let c=Array.from(e.childNodes),l=Array.from(t.childNodes);for(let[t,u]of l.entries()){let l=c[t];if(!l){e.appendChild(r(u,n,s));continue}if(!a(l,u)){e.replaceChild(r(u,n,s),l);continue}if(l instanceof Element&&u instanceof Element){i(l,u),o(l,u,n,s);continue}l.nodeValue!==u.nodeValue&&(l.nodeValue=u.nodeValue)}for(let e of c.slice(l.length))e.remove()}function s(e,t){let n={inertScriptSubtrees:0},r=e.ownerDocument??globalThis.document,i=r.createElement(`template`);return i.innerHTML=t,o(e,i.content,r,n),n}export{s as morphInto};
|
package/dist/form/index.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { FormEvent } from 'react';
|
|
|
2
2
|
import { Store } from 'effector';
|
|
3
3
|
import { ErrorCallback, FormInstance as CoreFormInstance, SubmitCallback, SuccessCallback } from '@picked/client/form';
|
|
4
4
|
import { ReactFormField } from '../utils/map-field-to-react';
|
|
5
|
+
import { FormCallbackOptions, FormCallbackRegistrars } from './use-form-callbacks';
|
|
6
|
+
export type { FormCallbackOptions, FormCallbackRegistrars, FormCallbackChannel, } from './use-form-callbacks';
|
|
5
7
|
export interface FormInstance {
|
|
6
8
|
form: {
|
|
7
9
|
props: {
|
|
@@ -9,11 +11,17 @@ export interface FormInstance {
|
|
|
9
11
|
};
|
|
10
12
|
errors: CoreFormInstance['form']['errors'];
|
|
11
13
|
submitting: CoreFormInstance['form']['submitting'];
|
|
12
|
-
onBeforeSubmit: (callback: SubmitCallback) =>
|
|
13
|
-
onSuccess: (callback: SuccessCallback) =>
|
|
14
|
-
onError: (callback: ErrorCallback) =>
|
|
14
|
+
onBeforeSubmit: (callback: SubmitCallback) => void;
|
|
15
|
+
onSuccess: (callback: SuccessCallback) => void;
|
|
16
|
+
onError: (callback: ErrorCallback) => void;
|
|
15
17
|
};
|
|
16
18
|
fields: Store<ReactFormField[]>;
|
|
19
|
+
/**
|
|
20
|
+
* Raw core registrars, so a hook that owns its own callback channel (`useStep`)
|
|
21
|
+
* can register its dispatcher directly against the core rather than nesting
|
|
22
|
+
* inside this hook's channel. Internal: not part of the supported surface.
|
|
23
|
+
*/
|
|
24
|
+
formRegistrars: FormCallbackRegistrars;
|
|
17
25
|
}
|
|
18
26
|
/**
|
|
19
27
|
* React hook mirroring Svelte's `createForm`: accepts reactive stores for
|
|
@@ -23,15 +31,18 @@ export interface FormInstance {
|
|
|
23
31
|
* lifetime is the hook instance, not the render — so `title` and `fieldKeys`
|
|
24
32
|
* are stores (reactive access over time) rather than values (snapshots).
|
|
25
33
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
34
|
+
* Callback lifetime is the hook instance, not the render: `onBeforeSubmit` /
|
|
35
|
+
* `onSuccess` / `onError` are safe to call from a render body and return `void`,
|
|
36
|
+
* because cleanup happens automatically when the hook unmounts. Prefer the
|
|
37
|
+
* `options` argument — it is latest-wins and has no registration side effect at
|
|
38
|
+
* all. See `use-form-callbacks.ts` for how this is achieved.
|
|
29
39
|
*
|
|
30
40
|
* @param {string} formId - Stable identifier for the form.
|
|
31
41
|
* @param {Store<string>} title - Store holding the form/step title.
|
|
32
42
|
* @param {Store<string[]>} fieldKeys - Store of field keys belonging to this form.
|
|
43
|
+
* @param {FormCallbackOptions} [options] - Latest-wins submit callback slots.
|
|
33
44
|
* @returns {FormInstance} React form with submit handler, mapped fields, and subscribable callbacks.
|
|
34
|
-
* @example const { form, fields } = useForm(stepKey, nameStore, fieldKeysStore)
|
|
45
|
+
* @example const { form, fields } = useForm(stepKey, nameStore, fieldKeysStore, { onSuccess })
|
|
35
46
|
*/
|
|
36
|
-
export declare function useForm(formId: string, title: Store<string>, fieldKeys: Store<string[]
|
|
47
|
+
export declare function useForm(formId: string, title: Store<string>, fieldKeys: Store<string[]>, options?: FormCallbackOptions): FormInstance;
|
|
37
48
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/form/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/form/index.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,OAAO,EAAE,KAAK,SAAS,EAAW,MAAM,OAAO,CAAA;AAC/C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,UAAU,CAAA;AACrC,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,YAAY,IAAI,gBAAgB,EACrC,KAAK,cAAc,EACnB,KAAK,eAAe,EACrB,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EAAmB,KAAK,cAAc,EAAE,MAAM,6BAA6B,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/form/index.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,OAAO,EAAE,KAAK,SAAS,EAAW,MAAM,OAAO,CAAA;AAC/C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,UAAU,CAAA;AACrC,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,YAAY,IAAI,gBAAgB,EACrC,KAAK,cAAc,EACnB,KAAK,eAAe,EACrB,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EAAmB,KAAK,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAElF,OAAO,EAEL,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC5B,MAAM,sBAAsB,CAAA;AAE7B,YAAY,EACV,mBAAmB,EACnB,sBAAsB,EACtB,mBAAmB,GACpB,MAAM,sBAAsB,CAAA;AAE7B,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE;QACJ,KAAK,EAAE;YACL,QAAQ,EAAE,CAAC,CAAC,EAAE,SAAS,CAAC,eAAe,CAAC,KAAK,IAAI,CAAA;SAClD,CAAA;QACD,MAAM,EAAE,gBAAgB,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,CAAA;QAC1C,UAAU,EAAE,gBAAgB,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAA;QAClD,cAAc,EAAE,CAAC,QAAQ,EAAE,cAAc,KAAK,IAAI,CAAA;QAClD,SAAS,EAAE,CAAC,QAAQ,EAAE,eAAe,KAAK,IAAI,CAAA;QAC9C,OAAO,EAAE,CAAC,QAAQ,EAAE,aAAa,KAAK,IAAI,CAAA;KAC3C,CAAA;IACD,MAAM,EAAE,KAAK,CAAC,cAAc,EAAE,CAAC,CAAA;IAC/B;;;;OAIG;IACH,cAAc,EAAE,sBAAsB,CAAA;CACvC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,OAAO,CACrB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,EACpB,SAAS,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,EAC1B,OAAO,CAAC,EAAE,mBAAmB,GAC5B,YAAY,CA2Cd"}
|
package/dist/form/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useExperience as e,usePicked as t}from"../hooks/client.js";import{mapFieldToReact as n}from"../utils/map-field-to-react.js";import{prepareNativeEvent as r}from"../utils/prepare-native-event.js";import{
|
|
1
|
+
import{useExperience as e,usePicked as t}from"../hooks/client.js";import{mapFieldToReact as n}from"../utils/map-field-to-react.js";import{prepareNativeEvent as r}from"../utils/prepare-native-event.js";import{useFormCallbacks as i}from"./use-form-callbacks.js";import{useMemo as a}from"react";import{createForm as o}from"@picked/client/form";function s(s,c,l,u){let d=t(),f=e(),p=a(()=>o({client:d,experienceInstance:f},s,c,l),[d,f,s,c,l]),m=a(()=>p.fields.map(e=>e.map(n)),[p]),h=a(()=>({onBeforeSubmit:e=>p.form.onBeforeSubmit(e),onSuccess:e=>p.form.onSuccess(e),onError:e=>p.form.onError(e)}),[p]),g=i(h,u);return{form:a(()=>({errors:p.form.errors,submitting:p.form.submitting,props:{onSubmit:e=>{e.preventDefault(),r(e.nativeEvent,e.currentTarget),p.form.props.onsubmit(e.nativeEvent)}},onBeforeSubmit:g.onBeforeSubmit,onSuccess:g.onSuccess,onError:g.onError}),[p,g]),fields:m,formRegistrars:h}}export{s as useForm};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ErrorCallback, FormCallbackOptions, SubmitCallback, SuccessCallback } from '@picked/client/form';
|
|
2
|
+
export type { FormCallbackOptions } from '@picked/client/form';
|
|
3
|
+
/**
|
|
4
|
+
* The core's append-only registration functions, each returning an unsubscribe.
|
|
5
|
+
*
|
|
6
|
+
* Passed down from `useForm` so a hook that owns its own channel — `useStep` —
|
|
7
|
+
* can register its single dispatcher directly against the core rather than
|
|
8
|
+
* nesting inside another channel.
|
|
9
|
+
*
|
|
10
|
+
* @example useFormCallbacks(stepContext.formRegistrars)
|
|
11
|
+
*/
|
|
12
|
+
export interface FormCallbackRegistrars {
|
|
13
|
+
onBeforeSubmit: (callback: SubmitCallback) => () => void;
|
|
14
|
+
onSuccess: (callback: SuccessCallback) => () => void;
|
|
15
|
+
onError: (callback: ErrorCallback) => () => void;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Lifecycle-safe registration surface handed to consumers.
|
|
19
|
+
*
|
|
20
|
+
* Nothing is returned because cleanup is automatic — the channel is torn down
|
|
21
|
+
* with the hook instance that owns it. This mirrors the Svelte adapter, whose
|
|
22
|
+
* registrations also return `void`.
|
|
23
|
+
*
|
|
24
|
+
* @example channel.onSuccess(({ data }) => track(data))
|
|
25
|
+
*/
|
|
26
|
+
export interface FormCallbackChannel {
|
|
27
|
+
onBeforeSubmit: (callback: SubmitCallback) => void;
|
|
28
|
+
onSuccess: (callback: SuccessCallback) => void;
|
|
29
|
+
onError: (callback: ErrorCallback) => void;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Owns a form-callback channel for one hook instance.
|
|
33
|
+
*
|
|
34
|
+
* Registers a single dispatcher per event against the core registrars and routes
|
|
35
|
+
* both the options slot and any imperative registrations through it, so consumer
|
|
36
|
+
* callbacks can never accumulate in the core registry across renders.
|
|
37
|
+
*
|
|
38
|
+
* @param {FormCallbackRegistrars | undefined} registrars - Core registration functions; when undefined the channel no-ops, so callers can invoke this hook unconditionally.
|
|
39
|
+
* @param {FormCallbackOptions} [options] - Latest-wins callback slots.
|
|
40
|
+
* @returns {FormCallbackChannel} A registration surface whose cleanup is automatic.
|
|
41
|
+
* @example const channel = useFormCallbacks(form.formRegistrars, { onSuccess })
|
|
42
|
+
* @design-parity Mirrors the Svelte adapter's `$effect`-wrapped registration.
|
|
43
|
+
*/
|
|
44
|
+
export declare function useFormCallbacks(registrars: FormCallbackRegistrars | undefined, options?: FormCallbackOptions): FormCallbackChannel;
|
|
45
|
+
//# sourceMappingURL=use-form-callbacks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-form-callbacks.d.ts","sourceRoot":"","sources":["../../src/form/use-form-callbacks.ts"],"names":[],"mappings":"AAkCA,OAAO,KAAK,EACV,aAAa,EACb,mBAAmB,EACnB,cAAc,EACd,eAAe,EAChB,MAAM,qBAAqB,CAAA;AAK5B,YAAY,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAK9D;;;;;;;;GAQG;AACH,MAAM,WAAW,sBAAsB;IACrC,cAAc,EAAE,CAAC,QAAQ,EAAE,cAAc,KAAK,MAAM,IAAI,CAAA;IACxD,SAAS,EAAE,CAAC,QAAQ,EAAE,eAAe,KAAK,MAAM,IAAI,CAAA;IACpD,OAAO,EAAE,CAAC,QAAQ,EAAE,aAAa,KAAK,MAAM,IAAI,CAAA;CACjD;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,mBAAmB;IAClC,cAAc,EAAE,CAAC,QAAQ,EAAE,cAAc,KAAK,IAAI,CAAA;IAClD,SAAS,EAAE,CAAC,QAAQ,EAAE,eAAe,KAAK,IAAI,CAAA;IAC9C,OAAO,EAAE,CAAC,QAAQ,EAAE,aAAa,KAAK,IAAI,CAAA;CAC3C;AAuFD;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,sBAAsB,GAAG,SAAS,EAC9C,OAAO,CAAC,EAAE,mBAAmB,GAC5B,mBAAmB,CAgGrB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{useCallback as e,useEffect as t,useMemo as n,useRef as r}from"react";function i(){return{pass:[],sealedAt:null,persistent:[]}}function a(e){e.sealedAt!==null&&(e.persistent=[...e.persistent,...e.pass.slice(e.sealedAt)]),e.pass=[],e.sealedAt=null}function o(e,t){let n={callback:t};return e.pass=[...e.pass,n],()=>{e.pass=e.pass.filter(e=>e!==n),e.persistent=e.persistent.filter(e=>e!==n)}}function s(e,t){let n=[...e.pass,...e.persistent].map(e=>e.callback);return t===void 0?n:[t,...n]}function c(c,l){let u=r(null);u.current??={beforeSubmit:i(),success:i(),error:i(),options:{}};let d=u.current;a(d.beforeSubmit),a(d.success),a(d.error),t(()=>{d.options=l??{},d.beforeSubmit.sealedAt=d.beforeSubmit.pass.length,d.success.sealedAt=d.success.pass.length,d.error.sealedAt=d.error.pass.length});let f=e(async e=>{let t=e;for(let e of s(d.beforeSubmit,d.options.onBeforeSubmit)){let n=await e(t);n!==void 0&&(t=n)}return t},[d]),p=e(async e=>{await Promise.all(s(d.success,d.options.onSuccess).map(t=>t(e)))},[d]),m=e(async e=>{await Promise.all(s(d.error,d.options.onError).map(t=>t(e)))},[d]);return t(()=>{if(!c)return;let e=[c.onBeforeSubmit(f),c.onSuccess(p),c.onError(m)];return()=>{e.forEach(e=>e())}},[c,f,p,m]),n(()=>({onBeforeSubmit:e=>o(d.beforeSubmit,e),onSuccess:e=>o(d.success,e),onError:e=>o(d.error,e)}),[d])}export{c as useFormCallbacks};
|
package/dist/hooks/step.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Store } from 'effector';
|
|
2
2
|
import { Content, ExperienceStep } from '@picked/client';
|
|
3
3
|
import { FormInstance } from '../form';
|
|
4
|
+
import { FormCallbackOptions } from '../form/use-form-callbacks';
|
|
4
5
|
import { ReactFormField } from '../utils/map-field-to-react';
|
|
5
6
|
export interface StepContext {
|
|
6
7
|
step: Store<ExperienceStep>;
|
|
@@ -25,15 +26,29 @@ export interface StepContext {
|
|
|
25
26
|
/** @deprecated Use `isActive` instead. */
|
|
26
27
|
isActiveStep: boolean;
|
|
27
28
|
form: FormInstance['form'];
|
|
29
|
+
/**
|
|
30
|
+
* Raw core registrars, used by `useStep` to own a callback channel scoped to the
|
|
31
|
+
* consuming component rather than to `Step`. Internal: not part of the
|
|
32
|
+
* supported surface.
|
|
33
|
+
*/
|
|
34
|
+
formRegistrars: FormInstance['formRegistrars'];
|
|
28
35
|
}
|
|
29
36
|
export declare const StepContextProvider: import('react').Context<StepContext | null>;
|
|
30
37
|
/**
|
|
31
38
|
* Retrieves the step context from the nearest provider.
|
|
32
39
|
*
|
|
40
|
+
* The returned `form`'s callback registration is scoped to **this** hook
|
|
41
|
+
* instance, not to the enclosing `Step`. That distinction is load-bearing: a
|
|
42
|
+
* component subscribed to field state re-renders on every keystroke while `Step`
|
|
43
|
+
* does not, so a channel anchored at `Step` would still accumulate one
|
|
44
|
+
* registration per consumer render. Callbacks are torn down when the consuming
|
|
45
|
+
* component unmounts, independently of sibling consumers.
|
|
46
|
+
*
|
|
47
|
+
* @param {FormCallbackOptions} [options] - Latest-wins submit callback slots; the recommended way to attach callbacks.
|
|
33
48
|
* @returns {StepContext} The current step context.
|
|
34
|
-
* @example useStep()
|
|
49
|
+
* @example const { form, fields } = useStep({ onSuccess: ({ data }) => track(data) })
|
|
35
50
|
*/
|
|
36
|
-
export declare function useStep(): StepContext;
|
|
51
|
+
export declare function useStep(options?: FormCallbackOptions): StepContext;
|
|
37
52
|
/**
|
|
38
53
|
* Creates and provides step context for the given step key.
|
|
39
54
|
*
|
|
@@ -42,10 +57,11 @@ export declare function useStep(): StepContext;
|
|
|
42
57
|
* step is registered — matching the Svelte SDK's behaviour.
|
|
43
58
|
*
|
|
44
59
|
* @param {string} stepKey - The step key to look up in the experience tree.
|
|
60
|
+
* @param {FormCallbackOptions} [options] - Latest-wins submit callback slots for the step's own form.
|
|
45
61
|
* @returns {StepContext} Step context with form, fields, and active state.
|
|
46
|
-
* @example useSetStepContext('step-1')
|
|
62
|
+
* @example useSetStepContext('step-1', { onSuccess: ({ data }) => track(data) })
|
|
47
63
|
*/
|
|
48
|
-
export declare function useSetStepContext(stepKey: string): StepContext;
|
|
64
|
+
export declare function useSetStepContext(stepKey: string, options?: FormCallbackOptions): StepContext;
|
|
49
65
|
/**
|
|
50
66
|
* Returns the current step's resolved CMS content data, or `null` when no content is registered.
|
|
51
67
|
*
|
package/dist/hooks/step.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"step.d.ts","sourceRoot":"","sources":["../../src/hooks/step.ts"],"names":[],"mappings":"AAEA,OAAO,EAAW,KAAK,KAAK,EAAE,MAAM,UAAU,CAAA;AAC9C,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAC7D,OAAO,EAAW,KAAK,YAAY,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"step.d.ts","sourceRoot":"","sources":["../../src/hooks/step.ts"],"names":[],"mappings":"AAEA,OAAO,EAAW,KAAK,KAAK,EAAE,MAAM,UAAU,CAAA;AAC9C,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAC7D,OAAO,EAAW,KAAK,YAAY,EAAE,MAAM,SAAS,CAAA;AACpD,OAAO,EAAoB,KAAK,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAEvF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAIjE,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,KAAK,CAAC,cAAc,CAAC,CAAA;IAC3B,kFAAkF;IAClF,MAAM,EAAE,KAAK,CAAC,cAAc,EAAE,CAAC,CAAA;IAC/B;;;;;OAKG;IACH,kBAAkB,EAAE,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,CAAA;IACzC;;;;;;OAMG;IACH,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAAA;IAC9C,QAAQ,EAAE,OAAO,CAAA;IACjB,0CAA0C;IAC1C,YAAY,EAAE,OAAO,CAAA;IACrB,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC,CAAA;IAC1B;;;;OAIG;IACH,cAAc,EAAE,YAAY,CAAC,gBAAgB,CAAC,CAAA;CAC/C;AAED,eAAO,MAAM,mBAAmB,6CAA0C,CAAA;AAE1E;;;;;;;;;;;;;GAaG;AACH,wBAAgB,OAAO,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,WAAW,CAiBlE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,WAAW,CAoE7F;AAgCD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAM/D"}
|
package/dist/hooks/step.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useExperience as e,usePicked as t}from"./client.js";import{useStoreGlobal as n}from"./use-store-global.js";import{
|
|
1
|
+
import{useExperience as e,usePicked as t}from"./client.js";import{useStoreGlobal as n}from"./use-store-global.js";import{useFormCallbacks as r}from"../form/use-form-callbacks.js";import{useForm as i}from"../form/index.js";import{useStepRouter as a}from"../router/context.js";import{createContext as o,useContext as s,useMemo as c}from"react";import{combine as l}from"effector";var u=o(null);function d(e){let t=s(u),n=r(t?.formRegistrars,e),i=c(()=>t?{...t,form:{...t.form,...n}}:null,[t,n]);if(!i)throw Error(`useStep must be used within a StepContextProvider`);return i}function f(n,r){let o=e(),s=t(),{activeStep:u}=a(),d=c(()=>u?.key===n,[u?.key,n]),f=c(()=>o.treeStore.map(e=>e.steps.find(e=>e.key===n)??{id:n,key:n,name:``,fieldKeys:[]},{skipVoid:!1}),[o,n]),{form:p,fields:m,formRegistrars:h}=i(n,c(()=>f.map(e=>e.name,{skipVoid:!1}),[f]),c(()=>f.map(e=>e.fieldKeys,{skipVoid:!1}),[f]),r),g=c(()=>l(f,s.contents.indexStore,(e,t)=>e.layoutContentId?t[e.layoutContentId]??null:null,{skipVoid:!1}),[f,s]);return{step:f,fields:m,layoutContentStore:g,content:c(()=>g.map(e=>e?.data??null,{skipVoid:!1}),[g]),isActive:d,isActiveStep:d,form:p,formRegistrars:h}}function p(){let n=e(),r=t();return c(()=>l(n.treeStore,r.contents.indexStore,(e,t)=>{let n=e.steps.find(e=>e.layoutContentId);return n?.layoutContentId?t[n.layoutContentId]??null:null}),[n,r])}function m(){let e=s(u),t=p();return n(e?.layoutContentStore??t)?.data??null}export{u as StepContextProvider,f as useSetStepContext,d as useStep,m as useStepContent};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@picked/react",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.70-next.2",
|
|
4
4
|
"devDependencies": {
|
|
5
5
|
"@picked/client": "workspace:*",
|
|
6
6
|
"@repo/eslint-config": "workspace:*",
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
},
|
|
86
86
|
"module": "./dist/index.js",
|
|
87
87
|
"peerDependencies": {
|
|
88
|
-
"@picked/client": "0.0.
|
|
88
|
+
"@picked/client": "0.0.70-next.2",
|
|
89
89
|
"effector": "^23.3.0",
|
|
90
90
|
"react": "19.2.2",
|
|
91
91
|
"react-dom": "19.2.2"
|