@picked/svelte 0.0.1-dev.1767659629925 → 0.0.1-dev.1767670478319
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/index.d.ts +16 -1
- package/dist/form/index.js +1 -1
- package/dist/hooks/action.d.ts +2 -0
- package/dist/hooks/action.js +1 -0
- package/dist/hooks/feature.d.ts +5 -0
- package/dist/hooks/feature.js +1 -0
- package/dist/hooks/index.d.ts +4 -2
- package/dist/hooks/index.js +1 -1
- package/dist/hooks/predicate.d.ts +3 -0
- package/dist/hooks/predicate.js +1 -0
- package/dist/hooks/step.d.ts +3 -20
- package/dist/hooks/step.js +1 -1
- package/package.json +2 -2
- package/dist/form/form.svelte.d.ts +0 -27
- package/dist/form/form.svelte.ts +0 -379
package/dist/form/index.d.ts
CHANGED
|
@@ -1 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
import { ValidationError } from '@picked/client';
|
|
2
|
+
interface GetFormOptions {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
onSuccess?: () => void | Promise<void>;
|
|
6
|
+
onError?: (errors: ValidationError[]) => void | Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
type SubmitEventWithCurrentTarget = SubmitEvent & {
|
|
9
|
+
currentTarget: EventTarget & HTMLFormElement;
|
|
10
|
+
};
|
|
11
|
+
export declare function getForm({ id: formId, title, onError, onSuccess }: GetFormOptions): {
|
|
12
|
+
form: {
|
|
13
|
+
onsubmit: import('effector').EventCallable<SubmitEventWithCurrentTarget>;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
export {};
|
package/dist/form/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{createEvent as k,createEffect as m,attach as b,sample as g,split as D}from"effector";import{getPicked as F,getExperience as S}from"../hooks/client.js";import"@picked/client";import{getStep as B}from"../hooks/step.js";import"svelte";import"svelte/store";import"@picked/analytics";function z({id:f,title:l,onError:d,onSuccess:h}){const o=F(),x=B(),n=S(),{currentStepStore:u}=n,v=k(),y=m(r=>o.responses.commit(r)),A=b({source:{step:u},effect:m(({step:r})=>{o.eventBus({type:"form_submit_success",form:{id:f,title:l},experience:n,step:r??void 0}),h==null||h()})}),_=b({source:{step:u},mapParams:(r,a)=>({step:a.step,errors:r.errors}),effect:m(({step:r,errors:a})=>{o.eventBus({type:"form_submit_error",form:{id:f,title:l},exception:{reason:a},experience:n,step:r??void 0}),a.forEach(p=>{o.eventBus({type:"field_validation_error",exception:{reason:[p]},experience:n,step:r??void 0})}),d==null||d(a)})});return g({clock:v,source:{step:u},fn:({step:r})=>({formId:f,title:l,step:r}),target:m(({formId:r,title:a,step:p})=>{o.eventBus({type:"form_submit",form:{id:r,title:a},experience:n,step:p??void 0})})}),g({clock:v,source:{fields:o.fields.listStore,stepFields:x.fields,step:u},fn:({stepFields:r},a)=>{a.preventDefault();const p=new FormData(a.currentTarget),E=Array.from(p.entries()),c={data:{},push(t){const s=t.questionId,e=this.data[s];e?Array.isArray(e)&&Array.isArray(t.value)?Array.isArray(e)&&Array.isArray(t.value)&&(typeof e[0]=="string"?this.data[s]=[...e,...t.value].map(String):typeof e[0]=="number"?this.data[s]=[...e,...t.value].map(Number):typeof e[0]=="boolean"?this.data[s]=[...e,...t.value].map(i=>!!i):this.data[s]=t.value):this.data[s]=t.value:this.data[s]=t.value}};return r.forEach(t=>{let s=E.filter(([e])=>e===t.id);s.length===0&&(s=[[t.id,""]]),s.forEach(([,e])=>{const i=t.id;if(e===""){c.push({questionId:i,value:null});return}switch(t.type){case"text":c.push({questionId:i,value:t.multiple?[String(e)]:String(e)});break;case"number":c.push({questionId:i,value:t.multiple?[Number(e)]:Number(e)});break;case"boolean":c.push({questionId:i,value:t.multiple?[e==="true"]:e==="true"});break}})}),c.data},target:y}),D({source:y.doneData,match:{hasErrors:({errors:r})=>r.length>0},cases:{hasErrors:_,__:A}}),{form:{onsubmit:v}}}export{z as getForm};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createStore as s,createEvent as f,attach as l,createEffect as u,sample as a}from"effector";import{getPicked as m}from"./client.js";function g(t){return m().actions.getEvokableAction(t)}function v(t){const c=g(t),o=s(void 0,{skipVoid:!1}),n=f(),r=l({source:c,mapParams:(e,i)=>i,effect:u(async e=>{if(e)return e()})});return a({clock:n,target:r}),a({clock:r.doneData,target:o}),n(),o}export{g as getAction,v as getActionResult};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function getFeature(featureName: string): import('effector').Store<() => Promise<boolean | string | undefined>>;
|
|
2
|
+
export declare function getFeatureResult(featureName: string): import('effector').StoreWritable<{
|
|
3
|
+
id: string;
|
|
4
|
+
bucket: string | boolean;
|
|
5
|
+
} | undefined>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createStore as i,createEvent as s,attach as f,createEffect as l,sample as o}from"effector";import{getPicked as v}from"./client.js";function d(t){return v().features.getEvokableFeature(t)}function m(t){const r=d(t),c=i(void 0,{skipVoid:!1}),a=s(),n=f({source:r,mapParams:(e,u)=>u,effect:l(async e=>await e())});return o({clock:a,target:n}),o({clock:n.doneData,fn:e=>{if(e!==void 0)return{id:t,bucket:e}},target:c}),a(),o({clock:r,target:a}),c}export{d as getFeature,m as getFeatureResult};
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { getPicked, getExperience } from './client';
|
|
2
|
-
export {
|
|
3
|
-
export {
|
|
2
|
+
export { getPredicate, getPredicateResult } from './predicate';
|
|
3
|
+
export { getFeature, getFeatureResult } from './feature';
|
|
4
|
+
export { getAction, getActionResult } from './action';
|
|
5
|
+
export { getStep, setStepContext } from './step';
|
|
4
6
|
export { useUnit, getContextScope, setContextScope } from './stores';
|
|
5
7
|
export { createSessionStorageStore, createLocalStorageStore } from './storage.js';
|
|
6
8
|
export { getTrackRecommendationPresentEvent } from './events';
|
package/dist/hooks/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getExperience as r,getPicked as o}from"./client.js";import{
|
|
1
|
+
import{getExperience as r,getPicked as o}from"./client.js";import{getPredicate as p,getPredicateResult as a}from"./predicate.js";import{getFeature as x,getFeatureResult as n}from"./feature.js";import{getAction as s,getActionResult as f}from"./action.js";import{getStep as u,setStepContext as S}from"./step.js";import{getContextScope as d,setContextScope as l,useUnit as C}from"./stores.js";import{createLocalStorageStore as y,createSessionStorageStore as k}from"./storage.js";import{getTrackRecommendationPresentEvent as A}from"./events.js";import{createQuery as F,queryCache as h,queryPresets as v}from"./create-query/index.js";export{y as createLocalStorageStore,F as createQuery,k as createSessionStorageStore,s as getAction,f as getActionResult,d as getContextScope,r as getExperience,x as getFeature,n as getFeatureResult,o as getPicked,p as getPredicate,a as getPredicateResult,u as getStep,A as getTrackRecommendationPresentEvent,h as queryCache,v as queryPresets,l as setContextScope,S as setStepContext,C as useUnit};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { Predicate } from '@picked/client';
|
|
2
|
+
export declare function getPredicate(predicateName: string): import('effector').Store<() => ReturnType<Predicate>>;
|
|
3
|
+
export declare function getPredicateResult(predicateName: string): import('effector').StoreWritable<string | boolean | string[] | undefined>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"@picked/client";import{createStore as s,createEvent as d,attach as l,createEffect as f,sample as a}from"effector";import{getPicked as u}from"./client.js";function m(e){return u().predicates.getEvokablePredicate(e)}function v(e){const t=m(e),o=s(void 0,{skipVoid:!1}),r=d(),i=l({source:t,mapParams:(c,n)=>n,effect:f(async c=>{if(c)return await c()})});return a({clock:r,target:i}),a({clock:i.doneData,target:o}),r(),a({clock:t,target:r}),o}export{m as getPredicate,v as getPredicateResult};
|
package/dist/hooks/step.d.ts
CHANGED
|
@@ -1,30 +1,13 @@
|
|
|
1
1
|
import { Store } from 'effector';
|
|
2
2
|
import { ExperienceStep, Field, Response } from '@picked/client';
|
|
3
|
-
|
|
4
|
-
export type FieldWithValue = Field & {
|
|
3
|
+
type FieldWithValue = Field & {
|
|
5
4
|
value: Response | undefined;
|
|
6
5
|
};
|
|
7
6
|
export interface StepContext {
|
|
8
7
|
step: Store<ExperienceStep>;
|
|
9
8
|
fields: Store<FieldWithValue[]>;
|
|
10
9
|
isActiveStep: Store<boolean>;
|
|
11
|
-
form: ReturnType<typeof getForm>;
|
|
12
10
|
}
|
|
13
|
-
export declare function getStep():
|
|
14
|
-
form: {
|
|
15
|
-
onsubmit: import('effector').EventCallable<SubmitEvent & {
|
|
16
|
-
currentTarget: EventTarget & HTMLFormElement;
|
|
17
|
-
}>;
|
|
18
|
-
};
|
|
19
|
-
onFormSubmit: (callback: (data: import('@picked/client').CommitResponses) => import('@picked/client').CommitResponses | void | Promise<import('@picked/client').CommitResponses | void>) => void;
|
|
20
|
-
onFormSuccess: (callback: (options: {
|
|
21
|
-
data: import('@picked/client').CommitResponses;
|
|
22
|
-
stopNavigation: import('effector').EventCallable<void>;
|
|
23
|
-
}) => void | Promise<void>) => void;
|
|
24
|
-
onFormError: (callback: (errors: import('@picked/client').ValidationError[]) => void | Promise<void>) => void;
|
|
25
|
-
isFormSubmitting: import('effector').StoreWritable<boolean>;
|
|
26
|
-
step: Store<ExperienceStep>;
|
|
27
|
-
fields: Store<FieldWithValue[]>;
|
|
28
|
-
isActiveStep: Store<boolean>;
|
|
29
|
-
};
|
|
11
|
+
export declare function getStep(): StepContext;
|
|
30
12
|
export declare function setStepContext(stepId: string): StepContext;
|
|
13
|
+
export {};
|
package/dist/hooks/step.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getContext as
|
|
1
|
+
import{getContext as v,setContext as E}from"svelte";import{createStore as h,combine as a,attach as w,createEffect as C,sample as l}from"effector";import{getStepFields as A}from"@picked/client";import{STEP_CONTEXT_KEY as d}from"../context.js";import{getStepRouter as F}from"../router/context.js";import{getExperience as T,getPicked as P}from"./client.js";function N(){return v(d)}function O(o){const u=T(),i=P(),{activeStep:m}=F(),n=m.map(e=>(e==null?void 0:e.id)===o),s=u.treeStore.map(e=>{const t=e.steps.find(r=>r.id===o);if(!t)throw new Error(`Step with id "${o}" not found in the experience tree.`);return t}),c=h([]),S=a({fields:c,responses:i.responses.listStore},({fields:e,responses:t})=>e.map(r=>{var f;const g=(f=t.find(x=>x.fieldId===r.id))==null?void 0:f.value;return{...r,value:g}})),p=w({source:a({step:s,fields:i.fields.listStore}),effect:C(async({step:{fieldIds:e},fields:t})=>await A(i,t.filter(r=>e.includes(r.id))))});return l({source:{isActiveStep:n},filter:({isActiveStep:e})=>e,target:p}),l({source:p.doneData,target:c}),E(d,{step:s,fields:S,isActiveStep:n})}export{N as getStep,O as setStepContext};
|
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.1767670478319",
|
|
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.1767670478319",
|
|
79
79
|
"effector": "^23.3.0",
|
|
80
80
|
"svelte": "^5.34.3"
|
|
81
81
|
},
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { CommitResponses, Field, ValidationError } from '@picked/client';
|
|
2
|
-
import { EventCallable, Store } from 'effector';
|
|
3
|
-
import { FieldWithValue } from '../hooks';
|
|
4
|
-
interface GetFormOptions {
|
|
5
|
-
id: string;
|
|
6
|
-
title: string;
|
|
7
|
-
fields: Store<Field[] | FieldWithValue[]>;
|
|
8
|
-
}
|
|
9
|
-
type SuccessCallback = (options: {
|
|
10
|
-
data: CommitResponses;
|
|
11
|
-
stopNavigation: EventCallable<void>;
|
|
12
|
-
}) => void | Promise<void>;
|
|
13
|
-
type ErrorCallback = (errors: ValidationError[]) => void | Promise<void>;
|
|
14
|
-
type SubmitCallback = (data: CommitResponses) => CommitResponses | void | Promise<CommitResponses | void>;
|
|
15
|
-
type SubmitEventWithCurrentTarget = SubmitEvent & {
|
|
16
|
-
currentTarget: EventTarget & HTMLFormElement;
|
|
17
|
-
};
|
|
18
|
-
export declare function getForm({ id: formId, title, fields }: GetFormOptions): {
|
|
19
|
-
form: {
|
|
20
|
-
onsubmit: EventCallable<SubmitEventWithCurrentTarget>;
|
|
21
|
-
};
|
|
22
|
-
onFormSubmit: (callback: SubmitCallback) => void;
|
|
23
|
-
onFormSuccess: (callback: SuccessCallback) => void;
|
|
24
|
-
onFormError: (callback: ErrorCallback) => void;
|
|
25
|
-
isFormSubmitting: import('effector').StoreWritable<boolean>;
|
|
26
|
-
};
|
|
27
|
-
export {};
|
package/dist/form/form.svelte.ts
DELETED
|
@@ -1,379 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
CommitResponses,
|
|
3
|
-
ExperienceStep,
|
|
4
|
-
Field,
|
|
5
|
-
FieldValue,
|
|
6
|
-
ValidationError,
|
|
7
|
-
ExperienceInstance,
|
|
8
|
-
} from '@picked/client'
|
|
9
|
-
import {
|
|
10
|
-
attach,
|
|
11
|
-
createEffect,
|
|
12
|
-
createEvent,
|
|
13
|
-
createStore,
|
|
14
|
-
sample,
|
|
15
|
-
split,
|
|
16
|
-
type EventCallable,
|
|
17
|
-
type Store,
|
|
18
|
-
} from 'effector'
|
|
19
|
-
import { getPicked, getExperience, type FieldWithValue } from '../hooks'
|
|
20
|
-
|
|
21
|
-
interface Response {
|
|
22
|
-
questionId: Field['id']
|
|
23
|
-
value: FieldValue
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
interface GetFormOptions {
|
|
27
|
-
id: string
|
|
28
|
-
title: string
|
|
29
|
-
fields: Store<Field[] | FieldWithValue[]>
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
interface CommitResult {
|
|
33
|
-
data: CommitResponses
|
|
34
|
-
errors: ValidationError[]
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
type SuccessCallback = (options: {
|
|
38
|
-
data: CommitResponses
|
|
39
|
-
stopNavigation: EventCallable<void>
|
|
40
|
-
}) => void | Promise<void>
|
|
41
|
-
type ErrorCallback = (errors: ValidationError[]) => void | Promise<void>
|
|
42
|
-
type SubmitCallback = (
|
|
43
|
-
data: CommitResponses,
|
|
44
|
-
) => CommitResponses | void | Promise<CommitResponses | void>
|
|
45
|
-
|
|
46
|
-
type SubmitEventWithCurrentTarget = SubmitEvent & { currentTarget: EventTarget & HTMLFormElement }
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Factory function to create a callback observer with automatic cleanup
|
|
50
|
-
* Reduces duplication across success/error/submit callback patterns
|
|
51
|
-
*/
|
|
52
|
-
function createObserver<TCallback extends (...args: any[]) => any, TReturn = void>(
|
|
53
|
-
executor: (callbacks: TCallback[], ...params: Parameters<TCallback>) => Promise<TReturn>,
|
|
54
|
-
) {
|
|
55
|
-
let callbacks: TCallback[] = []
|
|
56
|
-
|
|
57
|
-
const executeFx = createEffect(async (...params: Parameters<TCallback>): Promise<TReturn> => {
|
|
58
|
-
return await executor(callbacks, ...params)
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
const register = (callback: TCallback) => {
|
|
62
|
-
$effect(() => {
|
|
63
|
-
callbacks.push(callback)
|
|
64
|
-
return () => {
|
|
65
|
-
callbacks = callbacks.filter((cb) => cb !== callback)
|
|
66
|
-
}
|
|
67
|
-
})
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return { executeFx, register }
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export function getForm({ id: formId, title, fields }: GetFormOptions) {
|
|
74
|
-
const client = getPicked()
|
|
75
|
-
const experienceInstance: ExperienceInstance = getExperience()
|
|
76
|
-
const { currentStepStore: stepStore, goToNextStep } = experienceInstance
|
|
77
|
-
|
|
78
|
-
const submitEvent = createEvent<SubmitEventWithCurrentTarget>()
|
|
79
|
-
const formSuccessEvent = createEvent<CommitResponses>()
|
|
80
|
-
const formErrorEvent = createEvent<ValidationError[]>()
|
|
81
|
-
const stopNavigationEvent = createEvent<void>()
|
|
82
|
-
const allCallbacksCompleteEvent = createEvent<void>()
|
|
83
|
-
|
|
84
|
-
const isFormSubmitting = createStore<boolean>(false)
|
|
85
|
-
const stopNavigationStore = createStore(false)
|
|
86
|
-
stopNavigationStore
|
|
87
|
-
.on(stopNavigationEvent, () => true) // Set to true when stopNavigation is used in onFormSuccess
|
|
88
|
-
.on(submitEvent, () => false) // Reset to false on new submit
|
|
89
|
-
|
|
90
|
-
// Create observers for each callback type
|
|
91
|
-
const successObserver = createObserver<SuccessCallback>(async (callbacks, options) => {
|
|
92
|
-
await Promise.all(callbacks.map((callback) => Promise.resolve(callback(options))))
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
const errorObserver = createObserver<ErrorCallback>(async (callbacks, errors) => {
|
|
96
|
-
await Promise.all(callbacks.map((callback) => Promise.resolve(callback(errors))))
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
const submitObserver = createObserver<SubmitCallback, CommitResponses>(
|
|
100
|
-
async (callbacks, data) => {
|
|
101
|
-
let transformedData = data
|
|
102
|
-
|
|
103
|
-
// Apply each callback's transformation sequentially
|
|
104
|
-
for (const callback of callbacks) {
|
|
105
|
-
const result = await Promise.resolve(callback(transformedData))
|
|
106
|
-
// If callback returns data, use it; otherwise keep current
|
|
107
|
-
if (result !== undefined) {
|
|
108
|
-
transformedData = result
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return transformedData
|
|
113
|
-
},
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
const commitFx = createEffect(async (responses: CommitResponses) => {
|
|
117
|
-
const result = await client.responses.commit(responses)
|
|
118
|
-
// Merge submitted data with validation result
|
|
119
|
-
return {
|
|
120
|
-
data: responses,
|
|
121
|
-
errors: result.errors,
|
|
122
|
-
}
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
const onSuccessFx = attach({
|
|
126
|
-
source: { step: stepStore },
|
|
127
|
-
mapParams: (commitResult: CommitResult, source) => ({
|
|
128
|
-
step: source.step,
|
|
129
|
-
data: commitResult.data,
|
|
130
|
-
}),
|
|
131
|
-
effect: createEffect(
|
|
132
|
-
async ({ step, data }: { step: ExperienceStep | null; data: CommitResponses }) => {
|
|
133
|
-
client.eventBus({
|
|
134
|
-
type: 'form_submit_success',
|
|
135
|
-
form: { id: formId, title },
|
|
136
|
-
experience: experienceInstance,
|
|
137
|
-
step: step ?? undefined,
|
|
138
|
-
})
|
|
139
|
-
return data
|
|
140
|
-
},
|
|
141
|
-
),
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
const onErrorFx = attach({
|
|
145
|
-
source: { step: stepStore },
|
|
146
|
-
mapParams: (commitResult: CommitResult, source) => ({
|
|
147
|
-
step: source.step,
|
|
148
|
-
errors: commitResult.errors,
|
|
149
|
-
}),
|
|
150
|
-
effect: createEffect(
|
|
151
|
-
async ({ step, errors }: { step: ExperienceStep | null; errors: ValidationError[] }) => {
|
|
152
|
-
client.eventBus({
|
|
153
|
-
type: 'form_submit_error',
|
|
154
|
-
form: { id: formId, title },
|
|
155
|
-
exception: { reason: errors },
|
|
156
|
-
experience: experienceInstance,
|
|
157
|
-
step: step ?? undefined,
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
errors.forEach((exception) => {
|
|
161
|
-
client.eventBus({
|
|
162
|
-
type: 'field_validation_error',
|
|
163
|
-
exception: { reason: [exception] },
|
|
164
|
-
experience: experienceInstance,
|
|
165
|
-
step: step ?? undefined,
|
|
166
|
-
})
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
return errors
|
|
170
|
-
},
|
|
171
|
-
),
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
// set isFormSubmitting to true when the form is submitted
|
|
175
|
-
sample({
|
|
176
|
-
clock: submitEvent,
|
|
177
|
-
fn: () => true,
|
|
178
|
-
target: isFormSubmitting,
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
sample({
|
|
182
|
-
clock: submitEvent,
|
|
183
|
-
source: { step: stepStore },
|
|
184
|
-
fn: ({ step }) => ({ formId, title, step }),
|
|
185
|
-
target: createEffect(
|
|
186
|
-
({
|
|
187
|
-
formId: formIdParam,
|
|
188
|
-
title: titleParam,
|
|
189
|
-
step,
|
|
190
|
-
}: {
|
|
191
|
-
formId: string
|
|
192
|
-
title: string
|
|
193
|
-
step: ExperienceStep | null
|
|
194
|
-
}) => {
|
|
195
|
-
client.eventBus({
|
|
196
|
-
type: 'form_submit',
|
|
197
|
-
form: { id: formIdParam, title: titleParam },
|
|
198
|
-
experience: experienceInstance,
|
|
199
|
-
step: step ?? undefined,
|
|
200
|
-
})
|
|
201
|
-
},
|
|
202
|
-
),
|
|
203
|
-
})
|
|
204
|
-
|
|
205
|
-
sample({
|
|
206
|
-
clock: submitEvent,
|
|
207
|
-
source: {
|
|
208
|
-
fields: client.fields.listStore,
|
|
209
|
-
stepFields: fields,
|
|
210
|
-
},
|
|
211
|
-
fn: ({ stepFields }, event) => {
|
|
212
|
-
event.preventDefault()
|
|
213
|
-
|
|
214
|
-
const formData = new FormData(event.currentTarget)
|
|
215
|
-
const formDataEntries = Array.from(formData.entries())
|
|
216
|
-
|
|
217
|
-
const responses: { data: Record<Field['id'], FieldValue>; push: (item: Response) => void } = {
|
|
218
|
-
data: {},
|
|
219
|
-
push(item: Response) {
|
|
220
|
-
const questionId = item.questionId
|
|
221
|
-
const existing = this.data[questionId]
|
|
222
|
-
|
|
223
|
-
if (!existing) {
|
|
224
|
-
// No matching questionId, just add the new item
|
|
225
|
-
this.data[questionId] = item.value
|
|
226
|
-
// If both values are arrays, merge them
|
|
227
|
-
} else if (Array.isArray(existing) && Array.isArray(item.value)) {
|
|
228
|
-
if (Array.isArray(existing) && Array.isArray(item.value)) {
|
|
229
|
-
// Check what type FieldValue expects and convert accordingly
|
|
230
|
-
if (typeof existing[0] === 'string') {
|
|
231
|
-
this.data[questionId] = [...existing, ...item.value].map(String)
|
|
232
|
-
} else if (typeof existing[0] === 'number') {
|
|
233
|
-
this.data[questionId] = [...existing, ...item.value].map(Number)
|
|
234
|
-
} else if (typeof existing[0] === 'boolean') {
|
|
235
|
-
this.data[questionId] = [...existing, ...item.value].map((v) => Boolean(v))
|
|
236
|
-
} else {
|
|
237
|
-
this.data[questionId] = item.value
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
} else {
|
|
241
|
-
// Otherwise, override the existing value
|
|
242
|
-
this.data[questionId] = item.value
|
|
243
|
-
}
|
|
244
|
-
},
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
stepFields.forEach((field) => {
|
|
248
|
-
let fieldEntries = formDataEntries.filter(([key]) => key === field.id)
|
|
249
|
-
|
|
250
|
-
if (fieldEntries.length === 0) {
|
|
251
|
-
fieldEntries = [[field.id, '']]
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
fieldEntries.forEach(([, value]) => {
|
|
255
|
-
const questionId = field.id
|
|
256
|
-
|
|
257
|
-
if (value === '') {
|
|
258
|
-
responses.push({
|
|
259
|
-
questionId,
|
|
260
|
-
value: null,
|
|
261
|
-
})
|
|
262
|
-
|
|
263
|
-
return
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
switch (field.type) {
|
|
267
|
-
case 'text':
|
|
268
|
-
responses.push({
|
|
269
|
-
questionId,
|
|
270
|
-
value: field.multiple ? [String(value)] : String(value),
|
|
271
|
-
})
|
|
272
|
-
break
|
|
273
|
-
case 'number':
|
|
274
|
-
responses.push({
|
|
275
|
-
questionId,
|
|
276
|
-
value: field.multiple ? [Number(value)] : Number(value),
|
|
277
|
-
})
|
|
278
|
-
break
|
|
279
|
-
case 'boolean':
|
|
280
|
-
responses.push({
|
|
281
|
-
questionId,
|
|
282
|
-
value: field.multiple ? [value === 'true'] : value === 'true',
|
|
283
|
-
})
|
|
284
|
-
break
|
|
285
|
-
// case 'object':
|
|
286
|
-
// try {
|
|
287
|
-
// responses.push({
|
|
288
|
-
// questionId,
|
|
289
|
-
// value: field.multiple ? [JSON.parse(String(value))] : JSON.parse(String(value)),
|
|
290
|
-
// })
|
|
291
|
-
// } catch {
|
|
292
|
-
// responses.push({
|
|
293
|
-
// questionId,
|
|
294
|
-
// value: field.multiple ? [null] : null,
|
|
295
|
-
// })
|
|
296
|
-
// }
|
|
297
|
-
|
|
298
|
-
// break
|
|
299
|
-
}
|
|
300
|
-
})
|
|
301
|
-
})
|
|
302
|
-
|
|
303
|
-
return responses.data
|
|
304
|
-
},
|
|
305
|
-
target: submitObserver.executeFx,
|
|
306
|
-
})
|
|
307
|
-
|
|
308
|
-
// Pass transformed data from submit callbacks to commit
|
|
309
|
-
sample({
|
|
310
|
-
clock: submitObserver.executeFx.doneData,
|
|
311
|
-
target: commitFx,
|
|
312
|
-
})
|
|
313
|
-
|
|
314
|
-
// Errors are returned from the commit even if it is a success, split to handle errors array
|
|
315
|
-
split({
|
|
316
|
-
source: commitFx.doneData,
|
|
317
|
-
match: {
|
|
318
|
-
hasErrors: ({ errors }) => errors.length > 0,
|
|
319
|
-
},
|
|
320
|
-
cases: {
|
|
321
|
-
hasErrors: onErrorFx,
|
|
322
|
-
__: onSuccessFx,
|
|
323
|
-
},
|
|
324
|
-
})
|
|
325
|
-
|
|
326
|
-
sample({
|
|
327
|
-
clock: onSuccessFx.doneData,
|
|
328
|
-
target: formSuccessEvent,
|
|
329
|
-
})
|
|
330
|
-
|
|
331
|
-
// When form succeeds, execute all callbacks
|
|
332
|
-
sample({
|
|
333
|
-
clock: formSuccessEvent,
|
|
334
|
-
fn: (data) => ({ data, stopNavigation: stopNavigationEvent }), // Pass form data and event reference to callbacks
|
|
335
|
-
target: successObserver.executeFx,
|
|
336
|
-
})
|
|
337
|
-
|
|
338
|
-
// When all callbacks complete, trigger navigation check
|
|
339
|
-
sample({
|
|
340
|
-
clock: successObserver.executeFx.done,
|
|
341
|
-
target: allCallbacksCompleteEvent,
|
|
342
|
-
})
|
|
343
|
-
|
|
344
|
-
// Check navigation after all callbacks complete
|
|
345
|
-
sample({
|
|
346
|
-
clock: allCallbacksCompleteEvent,
|
|
347
|
-
source: stopNavigationStore,
|
|
348
|
-
filter: (stop) => !stop,
|
|
349
|
-
target: goToNextStep,
|
|
350
|
-
})
|
|
351
|
-
|
|
352
|
-
// Declarative event wiring for error flow
|
|
353
|
-
sample({
|
|
354
|
-
clock: onErrorFx.doneData,
|
|
355
|
-
target: formErrorEvent,
|
|
356
|
-
})
|
|
357
|
-
|
|
358
|
-
// When form errors, execute all error callbacks
|
|
359
|
-
sample({
|
|
360
|
-
clock: formErrorEvent,
|
|
361
|
-
target: errorObserver.executeFx,
|
|
362
|
-
})
|
|
363
|
-
|
|
364
|
-
sample({
|
|
365
|
-
clock: [allCallbacksCompleteEvent, errorObserver.executeFx.done],
|
|
366
|
-
fn: () => false,
|
|
367
|
-
target: isFormSubmitting,
|
|
368
|
-
})
|
|
369
|
-
|
|
370
|
-
return {
|
|
371
|
-
form: {
|
|
372
|
-
onsubmit: submitEvent,
|
|
373
|
-
},
|
|
374
|
-
onFormSubmit: submitObserver.register,
|
|
375
|
-
onFormSuccess: successObserver.register,
|
|
376
|
-
onFormError: errorObserver.register,
|
|
377
|
-
isFormSubmitting,
|
|
378
|
-
}
|
|
379
|
-
}
|