@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.
@@ -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};
@@ -1 +1 @@
1
- export { getForm } from './form.svelte';
1
+ export { getForm } from './form';
@@ -1 +1 @@
1
- import{getForm as e}from"./form.svelte";export{e as getForm};
1
+ import{getForm as e}from"./form";export{e as getForm};
@@ -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.svelte";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};
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};
@@ -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.svelte";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
+ 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.svelte";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};
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};
@@ -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.svelte";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};
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};
@@ -1,2 +1,2 @@
1
- export { default as StepRouter } from './StepRouter.svelte';
2
- export { default as Step } from './Step.svelte';
1
+ export { default as StepRouter } from './StepRouter';
2
+ export { default as Step } from './Step';
@@ -1 +1 @@
1
- import{default as o}from"./StepRouter.svelte";import{default as a}from"./Step.svelte";export{a as Step,o as 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.1767932013788",
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.1767932013788",
78
+ "@picked/client": "0.0.1-dev.1768169781062",
79
79
  "effector": "^23.3.0",
80
80
  "svelte": "^5.34.3"
81
81
  },
@@ -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