@picked/svelte 0.0.1-dev.1767932013788 → 0.0.1-dev.1768169781062
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/form/form.js +1 -0
- package/dist/form/index.d.ts +1 -1
- package/dist/form/index.js +1 -1
- package/dist/hooks/index.js +1 -1
- package/dist/hooks/step.js +1 -1
- package/dist/plugins/devtools/experience.js +1 -1
- package/dist/render/browser.js +1 -1
- package/dist/router/index.d.ts +2 -2
- package/dist/router/index.js +1 -1
- package/package.json +2 -2
- package/dist/form/form.svelte.ts +0 -457
- /package/dist/form/{form.svelte.d.ts → form.d.ts} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{getStepFields as W}from"@picked/client";import{createStore as y,combine as $,attach as A,createEffect as d,sample as n,createEvent as x,split as j}from"effector";import{getPicked as z,getExperience as G}from"../hooks/client.js";import"svelte";import"./form.svelte";import"svelte/store";import"@picked/analytics";function _(g){let u=[];return{executeFx:d(async(...a)=>await g(u,...a)),register:a=>{$effect(()=>(u.push(a),()=>{u=u.filter(p=>p!==a)}))}}}function U({id:g,title:u,fieldIds:w}){const l=z();let a=null,p,b=null;try{a=G(),p=a.currentStepStore,b=a.goToNextStep}catch{p=y(null)}const h=y([]),V=$({fields:h,responses:l.responses.listStore},({fields:e,responses:t})=>e.map(o=>{var c;const f=(c=t.find(r=>r.fieldId===o.id))==null?void 0:c.value;return{...o,value:f}})),N=A({source:l.fields.listStore,effect:d(async e=>{const t=e.filter(o=>w.some(f=>f===o.id));return await W(l,t)})});n({clock:N.doneData,target:h}),N();const v=x(),P=x(),B=x(),I=x(),F=x(),S=y(!1),O=y(!1);O.on(I,()=>!0).on(v,()=>!1);const k=_(async(e,t)=>{await Promise.all(e.map(o=>Promise.resolve(o(t))))}),E=_(async(e,t)=>{await Promise.all(e.map(o=>Promise.resolve(o(t))))}),D=_(async(e,t)=>{let o=t;for(const f of e){const c=await Promise.resolve(f(o));c!==void 0&&(o=c)}return o}),q=d(async e=>{const t=await l.responses.commit(e);return{data:e,errors:t.errors}}),T=A({source:{step:p},mapParams:(e,t)=>({step:t.step,data:e.data}),effect:d(async({step:e,data:t})=>(a&&l.eventBus({type:"form_submit_success",form:{id:g,title:u},experience:a,step:e??void 0}),t))}),C=A({source:{step:p},mapParams:(e,t)=>({step:t.step,errors:e.errors}),effect:d(async({step:e,errors:t})=>(a&&(l.eventBus({type:"form_submit_error",form:{id:g,title:u},exception:{reason:t},experience:a,step:e??void 0}),t.forEach(o=>{l.eventBus({type:"field_validation_error",exception:{reason:[o]},experience:a,step:e??void 0})})),t))});return n({clock:v,fn:()=>!0,target:S}),n({clock:v,source:{step:p},fn:({step:e})=>({formId:g,title:u,step:e}),target:d(({formId:e,title:t,step:o})=>{a&&l.eventBus({type:"form_submit",form:{id:e,title:t},experience:a,step:o??void 0})})}),n({clock:v,source:{stepFields:h},fn:({stepFields:e},t)=>{t.preventDefault();const o=new FormData(t.currentTarget),f=Array.from(o.entries()),c={data:{},push(r){const i=r.questionId,s=this.data[i];s?Array.isArray(s)&&Array.isArray(r.value)?Array.isArray(s)&&Array.isArray(r.value)&&(typeof s[0]=="string"?this.data[i]=[...s,...r.value].map(String):typeof s[0]=="number"?this.data[i]=[...s,...r.value].map(Number):typeof s[0]=="boolean"?this.data[i]=[...s,...r.value].map(m=>!!m):this.data[i]=r.value):this.data[i]=r.value:this.data[i]=r.value}};return e.forEach(r=>{let i=f.filter(([s])=>s===r.id);i.length===0&&(i=[[r.id,""]]),i.forEach(([,s])=>{const m=r.id;if(s===""){c.push({questionId:m,value:null});return}switch(r.type){case"text":c.push({questionId:m,value:r.multiple?[String(s)]:String(s)});break;case"number":c.push({questionId:m,value:r.multiple?[Number(s)]:Number(s)});break;case"boolean":c.push({questionId:m,value:r.multiple?[s==="true"]:s==="true"});break}})}),c.data},target:D.executeFx}),n({clock:D.executeFx.doneData,target:q}),j({source:q.doneData,match:{hasErrors:({errors:e})=>e.length>0},cases:{hasErrors:C,__:T}}),n({clock:T.doneData,target:P}),n({clock:P,fn:e=>({data:e,stopNavigation:I}),target:k.executeFx}),n({clock:k.executeFx.done,target:F}),b&&n({clock:F,source:O,filter:e=>!e,target:b}),n({clock:C.doneData,target:B}),n({clock:B,target:E.executeFx}),n({clock:[F,E.executeFx.done],fn:()=>!1,target:S}),{form:{onsubmit:v},fields:V,onFormSubmit:D.register,onFormSuccess:k.register,onFormError:E.register,isFormSubmitting:S}}export{U as getForm};
|
package/dist/form/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { getForm } from './form
|
|
1
|
+
export { getForm } from './form';
|
package/dist/form/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getForm as e}from"./form
|
|
1
|
+
import{getForm as e}from"./form";export{e as getForm};
|
package/dist/hooks/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getExperience as r,getPicked as o}from"./client.js";import{getStep as c,setStepContext as g}from"./step.js";import{getField as m}from"./field.js";import{getForm as n}from"../form/form
|
|
1
|
+
import{getExperience as r,getPicked as o}from"./client.js";import{getStep as c,setStepContext as g}from"./step.js";import{getField as m}from"./field.js";import{getForm as n}from"../form/form";import{getAction as s,getActionResult as i}from"./action.js";import{getFeature as S,getFeatureResult as d}from"./feature.js";import{getPredicate as P,getPredicateResult as C}from"./predicate.js";import{getContextScope as R,setContextScope as y,useUnit as k}from"./stores.js";import{createLocalStorageStore as A,createSessionStorageStore as E}from"./storage.js";import{getTrackRecommendationPresentEvent as v}from"./events.js";import{createQuery as Q,queryCache as T,queryPresets as U}from"./create-query/index.js";export{A as createLocalStorageStore,Q as createQuery,E as createSessionStorageStore,s as getAction,i as getActionResult,R as getContextScope,r as getExperience,S as getFeature,d as getFeatureResult,m as getField,n as getForm,o as getPicked,P as getPredicate,C as getPredicateResult,c as getStep,v as getTrackRecommendationPresentEvent,T as queryCache,U as queryPresets,y as setContextScope,g as setStepContext,k as useUnit};
|
package/dist/hooks/step.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getContext as x,setContext as u}from"svelte";import{STEP_CONTEXT_KEY as n}from"../context.js";import{getStepRouter as a}from"../router/context.js";import{getForm as g}from"../form/form
|
|
1
|
+
import{getContext as x,setContext as u}from"svelte";import{STEP_CONTEXT_KEY as n}from"../context.js";import{getStepRouter as a}from"../router/context.js";import{getForm as g}from"../form/form";import{getExperience as E}from"./client.js";function w(){return x(n)}function T(t){const i=E(),{activeStep:c}=a(),m=c.map(e=>(e==null?void 0:e.id)===t),o=i.treeStore.map(e=>{const r=e.steps.find(s=>s.id===t);if(!r)throw new Error(`Step with id "${t}" not found in the experience tree.`);return r}),{label:p,fieldIds:f}=o.getState(),S=g({id:`${t}`,title:p||t,fieldIds:f});return u(n,{step:o,isActiveStep:m,...S})}export{w as getStep,T as setStepContext};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createExperience as e}from"@picked/client/plugins/create-experience";import{adaptSvelteComponent as o}from"../../component.js";import t from"./DevToolsExperience
|
|
1
|
+
import{createExperience as e}from"@picked/client/plugins/create-experience";import{adaptSvelteComponent as o}from"../../component.js";import t from"./DevToolsExperience";const p=e({id:"svelte-devtools",name:"Picked Devtools",decide:{},targets:[{selector:"main",placement:"append"}],component:o(t)});p.addStep({label:"Tools"});export{p as devtoolsExperience};
|
package/dist/render/browser.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{mount as d}from"svelte";import{isValidAdaptedComponent as E}from"../component.js";import{CLIENT_CONTEXT_KEY as f,EXPERIENCE_CONTEXT_KEY as y}from"../context.js";import{mergeArrays as u}from"../utils/array.js";import h from"../utils/ErrorBoundary
|
|
1
|
+
import{mount as d}from"svelte";import{isValidAdaptedComponent as E}from"../component.js";import{CLIENT_CONTEXT_KEY as f,EXPERIENCE_CONTEXT_KEY as y}from"../context.js";import{mergeArrays as u}from"../utils/array.js";import h from"../utils/ErrorBoundary";function C(n,e){return new Map([[f,n],[y,e]])}const k=async(n,e,s,o,i)=>{var p,a;if(!E(e))throw new Error("The component is invalid.");const c=u((p=e.style)==null?void 0:p.sheets,i.stylesheets),m=u((a=e.style)==null?void 0:a.css,i.css);c.length&&c.forEach(r=>{const t=document.createElement("link");t.setAttribute("rel","stylesheet"),t.setAttribute("href",r),t.setAttribute("type","text/css"),t.setAttribute("picked-injected","true"),n.appendChild(t)}),m.length&&m.forEach(r=>{const t=document.createElement("style");t.textContent=r,t.setAttribute("type","text/css"),t.setAttribute("picked-injected","true"),n.appendChild(t)});const l=r=>{d(h,{target:n,props:{component:r,componentProps:{client:s,experience:o}},context:C(s,o)})};if(e.component instanceof Promise){const{default:r}=await e.component;l(r);return}l(e.component)},N=n=>{};export{C as createContext,k as renderSvelteComponent,N as unRenderSvelteComponent};
|
package/dist/router/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { default as StepRouter } from './StepRouter
|
|
2
|
-
export { default as Step } from './Step
|
|
1
|
+
export { default as StepRouter } from './StepRouter';
|
|
2
|
+
export { default as Step } from './Step';
|
package/dist/router/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{default as o}from"./StepRouter
|
|
1
|
+
import{default as o}from"./StepRouter";import{default as a}from"./Step";export{a as Step,o as StepRouter};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@picked/svelte",
|
|
3
|
-
"version": "0.0.1-dev.
|
|
3
|
+
"version": "0.0.1-dev.1768169781062",
|
|
4
4
|
"dependencies": {
|
|
5
5
|
"d3": "^7.9.0"
|
|
6
6
|
},
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
],
|
|
76
76
|
"module": "./dist/index.js",
|
|
77
77
|
"peerDependencies": {
|
|
78
|
-
"@picked/client": "0.0.1-dev.
|
|
78
|
+
"@picked/client": "0.0.1-dev.1768169781062",
|
|
79
79
|
"effector": "^23.3.0",
|
|
80
80
|
"svelte": "^5.34.3"
|
|
81
81
|
},
|
package/dist/form/form.svelte.ts
DELETED
|
@@ -1,457 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ExperienceStep,
|
|
3
|
-
Field,
|
|
4
|
-
FieldValue,
|
|
5
|
-
ValidationError,
|
|
6
|
-
ExperienceInstance,
|
|
7
|
-
} from '@picked/client'
|
|
8
|
-
import { getStepFields } from '@picked/client'
|
|
9
|
-
import {
|
|
10
|
-
attach,
|
|
11
|
-
createEffect,
|
|
12
|
-
createEvent,
|
|
13
|
-
createStore,
|
|
14
|
-
sample,
|
|
15
|
-
split,
|
|
16
|
-
type EventCallable,
|
|
17
|
-
type Store,
|
|
18
|
-
combine,
|
|
19
|
-
} from 'effector'
|
|
20
|
-
import { getPicked, getExperience } from '../hooks'
|
|
21
|
-
|
|
22
|
-
interface Response {
|
|
23
|
-
questionId: Field['id']
|
|
24
|
-
value: FieldValue
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Extract field IDs from a readonly string array
|
|
28
|
-
type ExtractFieldIds<TFieldIds extends readonly string[]> = TFieldIds[number]
|
|
29
|
-
|
|
30
|
-
interface GetFormOptions<TFieldIds extends readonly string[]> {
|
|
31
|
-
id: string
|
|
32
|
-
title: string
|
|
33
|
-
fieldIds: TFieldIds
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
interface CommitResult<TFieldIds extends string = string> {
|
|
37
|
-
data: Record<TFieldIds, FieldValue>
|
|
38
|
-
errors: ValidationError[]
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
type SuccessCallback<TFieldIds extends string = string> = (options: {
|
|
42
|
-
data: Record<TFieldIds, FieldValue>
|
|
43
|
-
stopNavigation: EventCallable<void>
|
|
44
|
-
}) => void | Promise<void>
|
|
45
|
-
|
|
46
|
-
type ErrorCallback = (errors: ValidationError[]) => void | Promise<void>
|
|
47
|
-
|
|
48
|
-
type SubmitCallback<TFieldIds extends string = string> = (
|
|
49
|
-
data: Record<TFieldIds, FieldValue>,
|
|
50
|
-
) => Record<TFieldIds, FieldValue> | void | Promise<Record<TFieldIds, FieldValue> | void>
|
|
51
|
-
|
|
52
|
-
type SubmitEventWithCurrentTarget = SubmitEvent & { currentTarget: EventTarget & HTMLFormElement }
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Factory function to create a callback observer with automatic cleanup
|
|
56
|
-
* Reduces duplication across success/error/submit callback patterns
|
|
57
|
-
*/
|
|
58
|
-
function createObserver<TCallback extends (...args: any[]) => any, TReturn = void>(
|
|
59
|
-
executor: (callbacks: TCallback[], ...params: Parameters<TCallback>) => Promise<TReturn>,
|
|
60
|
-
) {
|
|
61
|
-
let callbacks: TCallback[] = []
|
|
62
|
-
|
|
63
|
-
const executeFx = createEffect(async (...params: Parameters<TCallback>): Promise<TReturn> => {
|
|
64
|
-
return await executor(callbacks, ...params)
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
const register = (callback: TCallback) => {
|
|
68
|
-
$effect(() => {
|
|
69
|
-
callbacks.push(callback)
|
|
70
|
-
return () => {
|
|
71
|
-
callbacks = callbacks.filter((cb) => cb !== callback)
|
|
72
|
-
}
|
|
73
|
-
})
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return { executeFx, register }
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export function getForm<const TFieldIds extends readonly string[]>({
|
|
80
|
-
id: formId,
|
|
81
|
-
title,
|
|
82
|
-
fieldIds,
|
|
83
|
-
}: GetFormOptions<TFieldIds>) {
|
|
84
|
-
type FieldId = ExtractFieldIds<TFieldIds>
|
|
85
|
-
const client = getPicked()
|
|
86
|
-
|
|
87
|
-
// Make experience and router context optional
|
|
88
|
-
let experienceInstance: ExperienceInstance | null = null
|
|
89
|
-
let stepStore: Store<ExperienceStep | null>
|
|
90
|
-
let goToNextStep: EventCallable<void> | null = null
|
|
91
|
-
|
|
92
|
-
try {
|
|
93
|
-
experienceInstance = getExperience()
|
|
94
|
-
stepStore = experienceInstance.currentStepStore
|
|
95
|
-
goToNextStep = experienceInstance.goToNextStep
|
|
96
|
-
} catch {
|
|
97
|
-
// Not in experience context - create null store for standalone forms
|
|
98
|
-
stepStore = createStore<ExperienceStep | null>(null)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Create internal fields store
|
|
102
|
-
const fieldsStore = createStore<Field[]>([])
|
|
103
|
-
|
|
104
|
-
// Create fields with values store for UI
|
|
105
|
-
const fieldsWithValues = combine(
|
|
106
|
-
{
|
|
107
|
-
fields: fieldsStore,
|
|
108
|
-
responses: client.responses.listStore,
|
|
109
|
-
},
|
|
110
|
-
({ fields, responses }) => {
|
|
111
|
-
return fields.map((field) => {
|
|
112
|
-
const value = responses.find((r) => r.fieldId === field.id)?.value
|
|
113
|
-
return { ...field, value }
|
|
114
|
-
})
|
|
115
|
-
},
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
// Filter and evaluate fields based on fieldIds
|
|
119
|
-
const evaluateFieldsFx = attach({
|
|
120
|
-
source: client.fields.listStore,
|
|
121
|
-
effect: createEffect(async (allFields: Field[]) => {
|
|
122
|
-
const filteredFields = allFields.filter((field) =>
|
|
123
|
-
fieldIds.some((id) => id === field.id),
|
|
124
|
-
)
|
|
125
|
-
return await getStepFields(client, filteredFields)
|
|
126
|
-
}),
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
// Populate fields store when fields are evaluated
|
|
130
|
-
sample({
|
|
131
|
-
clock: evaluateFieldsFx.doneData,
|
|
132
|
-
target: fieldsStore,
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
// Auto-trigger field evaluation on form creation
|
|
136
|
-
evaluateFieldsFx()
|
|
137
|
-
|
|
138
|
-
const submitEvent = createEvent<SubmitEventWithCurrentTarget>()
|
|
139
|
-
const formSuccessEvent = createEvent<Record<FieldId, FieldValue>>()
|
|
140
|
-
const formErrorEvent = createEvent<ValidationError[]>()
|
|
141
|
-
const stopNavigationEvent = createEvent<void>()
|
|
142
|
-
const allCallbacksCompleteEvent = createEvent<void>()
|
|
143
|
-
|
|
144
|
-
const isFormSubmitting = createStore<boolean>(false)
|
|
145
|
-
const stopNavigationStore = createStore(false)
|
|
146
|
-
stopNavigationStore
|
|
147
|
-
.on(stopNavigationEvent, () => true) // Set to true when stopNavigation is used in onFormSuccess
|
|
148
|
-
.on(submitEvent, () => false) // Reset to false on new submit
|
|
149
|
-
|
|
150
|
-
// Create observers for each callback type
|
|
151
|
-
const successObserver = createObserver<SuccessCallback<FieldId>>(async (callbacks, options) => {
|
|
152
|
-
await Promise.all(callbacks.map((callback) => Promise.resolve(callback(options))))
|
|
153
|
-
})
|
|
154
|
-
|
|
155
|
-
const errorObserver = createObserver<ErrorCallback>(async (callbacks, errors) => {
|
|
156
|
-
await Promise.all(callbacks.map((callback) => Promise.resolve(callback(errors))))
|
|
157
|
-
})
|
|
158
|
-
|
|
159
|
-
const submitObserver = createObserver<SubmitCallback<FieldId>, Record<FieldId, FieldValue>>(
|
|
160
|
-
async (callbacks, data) => {
|
|
161
|
-
let transformedData = data
|
|
162
|
-
|
|
163
|
-
// Apply each callback's transformation sequentially
|
|
164
|
-
for (const callback of callbacks) {
|
|
165
|
-
const result = await Promise.resolve(callback(transformedData))
|
|
166
|
-
// If callback returns data, use it; otherwise keep current
|
|
167
|
-
if (result !== undefined) {
|
|
168
|
-
transformedData = result
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return transformedData
|
|
173
|
-
},
|
|
174
|
-
)
|
|
175
|
-
|
|
176
|
-
const commitFx = createEffect(async (responses: Record<FieldId, FieldValue>) => {
|
|
177
|
-
const result = await client.responses.commit(responses)
|
|
178
|
-
// Merge submitted data with validation result
|
|
179
|
-
return {
|
|
180
|
-
data: responses,
|
|
181
|
-
errors: result.errors,
|
|
182
|
-
}
|
|
183
|
-
})
|
|
184
|
-
|
|
185
|
-
const onSuccessFx = attach({
|
|
186
|
-
source: { step: stepStore },
|
|
187
|
-
mapParams: (commitResult: CommitResult<FieldId>, source) => ({
|
|
188
|
-
step: source.step,
|
|
189
|
-
data: commitResult.data,
|
|
190
|
-
}),
|
|
191
|
-
effect: createEffect(
|
|
192
|
-
async ({
|
|
193
|
-
step,
|
|
194
|
-
data,
|
|
195
|
-
}: {
|
|
196
|
-
step: ExperienceStep | null
|
|
197
|
-
data: Record<FieldId, FieldValue>
|
|
198
|
-
}) => {
|
|
199
|
-
// Only fire events if in experience context
|
|
200
|
-
if (experienceInstance) {
|
|
201
|
-
client.eventBus({
|
|
202
|
-
type: 'form_submit_success',
|
|
203
|
-
form: { id: formId, title },
|
|
204
|
-
experience: experienceInstance,
|
|
205
|
-
step: step ?? undefined,
|
|
206
|
-
})
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
return data
|
|
210
|
-
},
|
|
211
|
-
),
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
const onErrorFx = attach({
|
|
215
|
-
source: { step: stepStore },
|
|
216
|
-
mapParams: (commitResult: CommitResult<FieldId>, source) => ({
|
|
217
|
-
step: source.step,
|
|
218
|
-
errors: commitResult.errors,
|
|
219
|
-
}),
|
|
220
|
-
effect: createEffect(
|
|
221
|
-
async ({ step, errors }: { step: ExperienceStep | null; errors: ValidationError[] }) => {
|
|
222
|
-
// Only fire events if in experience context
|
|
223
|
-
if (experienceInstance) {
|
|
224
|
-
client.eventBus({
|
|
225
|
-
type: 'form_submit_error',
|
|
226
|
-
form: { id: formId, title },
|
|
227
|
-
exception: { reason: errors },
|
|
228
|
-
experience: experienceInstance,
|
|
229
|
-
step: step ?? undefined,
|
|
230
|
-
})
|
|
231
|
-
|
|
232
|
-
errors.forEach((exception) => {
|
|
233
|
-
client.eventBus({
|
|
234
|
-
type: 'field_validation_error',
|
|
235
|
-
exception: { reason: [exception] },
|
|
236
|
-
experience: experienceInstance,
|
|
237
|
-
step: step ?? undefined,
|
|
238
|
-
})
|
|
239
|
-
})
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
return errors
|
|
243
|
-
},
|
|
244
|
-
),
|
|
245
|
-
})
|
|
246
|
-
|
|
247
|
-
// set isFormSubmitting to true when the form is submitted
|
|
248
|
-
sample({
|
|
249
|
-
clock: submitEvent,
|
|
250
|
-
fn: () => true,
|
|
251
|
-
target: isFormSubmitting,
|
|
252
|
-
})
|
|
253
|
-
|
|
254
|
-
sample({
|
|
255
|
-
clock: submitEvent,
|
|
256
|
-
source: { step: stepStore },
|
|
257
|
-
fn: ({ step }) => ({ formId, title, step }),
|
|
258
|
-
target: createEffect(
|
|
259
|
-
({
|
|
260
|
-
formId: formIdParam,
|
|
261
|
-
title: titleParam,
|
|
262
|
-
step,
|
|
263
|
-
}: {
|
|
264
|
-
formId: string
|
|
265
|
-
title: string
|
|
266
|
-
step: ExperienceStep | null
|
|
267
|
-
}) => {
|
|
268
|
-
// Only fire events if in experience context
|
|
269
|
-
if (experienceInstance) {
|
|
270
|
-
client.eventBus({
|
|
271
|
-
type: 'form_submit',
|
|
272
|
-
form: { id: formIdParam, title: titleParam },
|
|
273
|
-
experience: experienceInstance,
|
|
274
|
-
step: step ?? undefined,
|
|
275
|
-
})
|
|
276
|
-
}
|
|
277
|
-
},
|
|
278
|
-
),
|
|
279
|
-
})
|
|
280
|
-
|
|
281
|
-
sample({
|
|
282
|
-
clock: submitEvent,
|
|
283
|
-
source: {
|
|
284
|
-
stepFields: fieldsStore,
|
|
285
|
-
},
|
|
286
|
-
fn: ({ stepFields }, event): Record<FieldId, FieldValue> => {
|
|
287
|
-
event.preventDefault()
|
|
288
|
-
|
|
289
|
-
const formData = new FormData(event.currentTarget)
|
|
290
|
-
const formDataEntries = Array.from(formData.entries())
|
|
291
|
-
|
|
292
|
-
const responses: { data: Record<Field['id'], FieldValue>; push: (item: Response) => void } = {
|
|
293
|
-
data: {},
|
|
294
|
-
push(item: Response) {
|
|
295
|
-
const questionId = item.questionId
|
|
296
|
-
const existing = this.data[questionId]
|
|
297
|
-
|
|
298
|
-
if (!existing) {
|
|
299
|
-
// No matching questionId, just add the new item
|
|
300
|
-
this.data[questionId] = item.value
|
|
301
|
-
// If both values are arrays, merge them
|
|
302
|
-
} else if (Array.isArray(existing) && Array.isArray(item.value)) {
|
|
303
|
-
if (Array.isArray(existing) && Array.isArray(item.value)) {
|
|
304
|
-
// Check what type FieldValue expects and convert accordingly
|
|
305
|
-
if (typeof existing[0] === 'string') {
|
|
306
|
-
this.data[questionId] = [...existing, ...item.value].map(String)
|
|
307
|
-
} else if (typeof existing[0] === 'number') {
|
|
308
|
-
this.data[questionId] = [...existing, ...item.value].map(Number)
|
|
309
|
-
} else if (typeof existing[0] === 'boolean') {
|
|
310
|
-
this.data[questionId] = [...existing, ...item.value].map((v) => Boolean(v))
|
|
311
|
-
} else {
|
|
312
|
-
this.data[questionId] = item.value
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
} else {
|
|
316
|
-
// Otherwise, override the existing value
|
|
317
|
-
this.data[questionId] = item.value
|
|
318
|
-
}
|
|
319
|
-
},
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
stepFields.forEach((field: Field) => {
|
|
323
|
-
let fieldEntries = formDataEntries.filter(([key]) => key === field.id)
|
|
324
|
-
|
|
325
|
-
if (fieldEntries.length === 0) {
|
|
326
|
-
fieldEntries = [[field.id, '']]
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
fieldEntries.forEach(([, value]) => {
|
|
330
|
-
const questionId = field.id
|
|
331
|
-
|
|
332
|
-
if (value === '') {
|
|
333
|
-
responses.push({
|
|
334
|
-
questionId,
|
|
335
|
-
value: null,
|
|
336
|
-
})
|
|
337
|
-
|
|
338
|
-
return
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
switch (field.type) {
|
|
342
|
-
case 'text':
|
|
343
|
-
responses.push({
|
|
344
|
-
questionId,
|
|
345
|
-
value: field.multiple ? [String(value)] : String(value),
|
|
346
|
-
})
|
|
347
|
-
break
|
|
348
|
-
case 'number':
|
|
349
|
-
responses.push({
|
|
350
|
-
questionId,
|
|
351
|
-
value: field.multiple ? [Number(value)] : Number(value),
|
|
352
|
-
})
|
|
353
|
-
break
|
|
354
|
-
case 'boolean':
|
|
355
|
-
responses.push({
|
|
356
|
-
questionId,
|
|
357
|
-
value: field.multiple ? [value === 'true'] : value === 'true',
|
|
358
|
-
})
|
|
359
|
-
break
|
|
360
|
-
// case 'object':
|
|
361
|
-
// try {
|
|
362
|
-
// responses.push({
|
|
363
|
-
// questionId,
|
|
364
|
-
// value: field.multiple ? [JSON.parse(String(value))] : JSON.parse(String(value)),
|
|
365
|
-
// })
|
|
366
|
-
// } catch {
|
|
367
|
-
// responses.push({
|
|
368
|
-
// questionId,
|
|
369
|
-
// value: field.multiple ? [null] : null,
|
|
370
|
-
// })
|
|
371
|
-
// }
|
|
372
|
-
|
|
373
|
-
// break
|
|
374
|
-
}
|
|
375
|
-
})
|
|
376
|
-
})
|
|
377
|
-
|
|
378
|
-
return responses.data
|
|
379
|
-
},
|
|
380
|
-
target: submitObserver.executeFx,
|
|
381
|
-
})
|
|
382
|
-
|
|
383
|
-
// Pass transformed data from submit callbacks to commit
|
|
384
|
-
sample({
|
|
385
|
-
clock: submitObserver.executeFx.doneData,
|
|
386
|
-
target: commitFx,
|
|
387
|
-
})
|
|
388
|
-
|
|
389
|
-
// Errors are returned from the commit even if it is a success, split to handle errors array
|
|
390
|
-
split({
|
|
391
|
-
source: commitFx.doneData,
|
|
392
|
-
match: {
|
|
393
|
-
hasErrors: ({ errors }) => errors.length > 0,
|
|
394
|
-
},
|
|
395
|
-
cases: {
|
|
396
|
-
hasErrors: onErrorFx,
|
|
397
|
-
__: onSuccessFx,
|
|
398
|
-
},
|
|
399
|
-
})
|
|
400
|
-
|
|
401
|
-
sample({
|
|
402
|
-
clock: onSuccessFx.doneData,
|
|
403
|
-
target: formSuccessEvent,
|
|
404
|
-
})
|
|
405
|
-
|
|
406
|
-
// When form succeeds, execute all callbacks
|
|
407
|
-
sample({
|
|
408
|
-
clock: formSuccessEvent,
|
|
409
|
-
fn: (data) => ({ data, stopNavigation: stopNavigationEvent }), // Pass form data and event reference to callbacks
|
|
410
|
-
target: successObserver.executeFx,
|
|
411
|
-
})
|
|
412
|
-
|
|
413
|
-
// When all callbacks complete, trigger navigation check
|
|
414
|
-
sample({
|
|
415
|
-
clock: successObserver.executeFx.done,
|
|
416
|
-
target: allCallbacksCompleteEvent,
|
|
417
|
-
})
|
|
418
|
-
|
|
419
|
-
// Check navigation after all callbacks complete (only if in router context)
|
|
420
|
-
if (goToNextStep) {
|
|
421
|
-
sample({
|
|
422
|
-
clock: allCallbacksCompleteEvent,
|
|
423
|
-
source: stopNavigationStore,
|
|
424
|
-
filter: (stop) => !stop,
|
|
425
|
-
target: goToNextStep,
|
|
426
|
-
})
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
// Declarative event wiring for error flow
|
|
430
|
-
sample({
|
|
431
|
-
clock: onErrorFx.doneData,
|
|
432
|
-
target: formErrorEvent,
|
|
433
|
-
})
|
|
434
|
-
|
|
435
|
-
// When form errors, execute all error callbacks
|
|
436
|
-
sample({
|
|
437
|
-
clock: formErrorEvent,
|
|
438
|
-
target: errorObserver.executeFx,
|
|
439
|
-
})
|
|
440
|
-
|
|
441
|
-
sample({
|
|
442
|
-
clock: [allCallbacksCompleteEvent, errorObserver.executeFx.done],
|
|
443
|
-
fn: () => false,
|
|
444
|
-
target: isFormSubmitting,
|
|
445
|
-
})
|
|
446
|
-
|
|
447
|
-
return {
|
|
448
|
-
form: {
|
|
449
|
-
onsubmit: submitEvent,
|
|
450
|
-
},
|
|
451
|
-
fields: fieldsWithValues,
|
|
452
|
-
onFormSubmit: submitObserver.register,
|
|
453
|
-
onFormSuccess: successObserver.register,
|
|
454
|
-
onFormError: errorObserver.register,
|
|
455
|
-
isFormSubmitting,
|
|
456
|
-
}
|
|
457
|
-
}
|
|
File without changes
|