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