@picked/svelte 0.0.1-dev.1773729274420 → 0.0.1-dev.1773807602703

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.
Files changed (36) hide show
  1. package/dist/form/index.d.ts +25 -2
  2. package/dist/form/index.js +1 -1
  3. package/dist/hooks/field.d.ts +3 -7
  4. package/dist/hooks/field.js +1 -1
  5. package/dist/hooks/step.d.ts +8 -8
  6. package/dist/hooks/step.js +1 -1
  7. package/dist/plugins/devtools/components/ExperienceInstanceListItem.svelte +17 -4
  8. package/dist/plugins/devtools/components/ExperienceInstancesTable.svelte +1 -1
  9. package/dist/plugins/devtools/screens/events/EventListItem.svelte +47 -47
  10. package/dist/plugins/devtools/screens/experiences/ExperienceDetailsScreen.svelte +1 -1
  11. package/dist/plugins/devtools/screens/experiences/ExperienceTreeScreen.svelte +10 -11
  12. package/dist/plugins/devtools/screens/experiences/ExperiencesListScreen.svelte +1 -1
  13. package/dist/plugins/devtools/screens/history/HistoryEntryListItem.svelte +6 -1
  14. package/dist/plugins/devtools/screens/responses/ResponseTableRow.svelte +10 -11
  15. package/dist/plugins/devtools/screens/responses/ResponsesTable.svelte +1 -1
  16. package/dist/router/Step.svelte +1 -0
  17. package/dist/router/StepRouter.svelte +1 -1
  18. package/dist/utils/get-step-fields.d.ts +2 -0
  19. package/dist/utils/get-step-fields.js +1 -0
  20. package/package.json +2 -2
  21. package/dist/form/build-responses.d.ts +0 -13
  22. package/dist/form/build-responses.js +0 -1
  23. package/dist/form/create-field-evaluator.d.ts +0 -17
  24. package/dist/form/create-field-evaluator.js +0 -1
  25. package/dist/form/create-field-validation.d.ts +0 -41
  26. package/dist/form/create-field-validation.js +0 -1
  27. package/dist/form/create-form.js +0 -1
  28. package/dist/form/create-form.svelte.d.ts +0 -41
  29. package/dist/form/create-form.svelte.js +0 -1
  30. package/dist/form/create-submit-pipeline.js +0 -1
  31. package/dist/form/create-submit-pipeline.svelte.d.ts +0 -53
  32. package/dist/form/create-submit-pipeline.svelte.js +0 -1
  33. package/dist/form/get-form.d.ts +0 -16
  34. package/dist/form/get-form.js +0 -1
  35. package/dist/form/get-step-fields.d.ts +0 -2
  36. package/dist/form/get-step-fields.js +0 -1
@@ -1,2 +1,25 @@
1
- export type { FormField } from './create-form.svelte';
2
- export { getForm } from './get-form';
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
+ /**
12
+ *
13
+ * @param root0
14
+ * @param root0.id
15
+ * @param root0.title
16
+ * @param root0.onError
17
+ * @param root0.onSuccess
18
+ * @example
19
+ */
20
+ export declare function getForm({ id: formId, title, onError, onSuccess }: GetFormOptions): {
21
+ form: {
22
+ onsubmit: import('effector').EventCallable<SubmitEventWithCurrentTarget>;
23
+ };
24
+ };
25
+ export {};
@@ -1 +1 @@
1
- import{getForm as e}from"./get-form.js";export{e as getForm};
1
+ import{createEvent as _,createEffect as m,attach as y,sample as v,split as k}from"effector";import{getPicked as D,getExperience as F}from"../hooks/client.js";import"@picked/client";import{getStep as w}from"../hooks/step.js";import"svelte";import"svelte/store";import"@picked/analytics";function z({id:f,title:l,onError:b,onSuccess:g}){const o=D(),x=w(),i=F(),{currentStepStore:u}=i,d=_(),h=m(r=>o.responses.commit(r)),E=y({source:{step:u},effect:m(async({step:r})=>{o.eventBus({type:"form_submit_success",form:{id:f,title:l},experience:i,step:r??void 0}),await g?.()})}),S=y({source:{step:u},mapParams:(r,a)=>({step:a.step,errors:r.errors}),effect:m(async({step:r,errors:a})=>{o.eventBus({type:"form_submit_error",form:{id:f,title:l},exception:{reason:a},experience:i,step:r??void 0}),a.forEach(c=>{o.eventBus({type:"field_validation_error",exception:{reason:[c]},experience:i,step:r??void 0})}),await b?.(a)})});return v({clock:d,source:{step:u},fn:({step:r})=>({formId:f,title:l,step:r}),target:m(({formId:r,title:a,step:c})=>{o.eventBus({type:"form_submit",form:{id:r,title:a},experience:i,step:c??void 0})})}),v({clock:d,source:{fields:o.fields.listStore,stepFields:x.fields,step:u},fn:({stepFields:r},a)=>{a.preventDefault();const c=new FormData(a.currentTarget),A=Array.from(c.entries()),p={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(n=>!!n):this.data[s]=t.value):this.data[s]=t.value:this.data[s]=t.value}};return r.forEach(t=>{let s=A.filter(([e])=>e===t.key||e===t.id);s.length===0&&(s=[[t.id,""]]),s.forEach(([,e])=>{const n=t.id;if(e===""){p.push({questionId:n,value:null});return}switch(t.type){case"text":p.push({questionId:n,value:t.multiple?[String(e)]:String(e)});break;case"number":p.push({questionId:n,value:t.multiple?[Number(e)]:Number(e)});break;case"boolean":p.push({questionId:n,value:t.multiple?[e==="true"]:e==="true"});break}})}),p.data},target:h}),k({source:h.doneData,match:{hasErrors:({errors:r})=>r.length>0},cases:{hasErrors:S,__:E}}),{form:{onsubmit:d}}}export{z as getForm};
@@ -1,12 +1,8 @@
1
1
  import { Field, FieldValue } from '@picked/client';
2
2
  import { Store } from 'effector';
3
- export interface GetFieldResponse {
3
+ interface GetFieldResponse {
4
4
  value: Store<FieldValue>;
5
5
  field: Store<Field | null>;
6
6
  }
7
- /**
8
- * @deprecated Field options and status are now available directly on the Field object
9
- * from the step fields store. Use `field.options`, `field.status`, and `field.error`
10
- * instead of accessing them through this hook.
11
- */
12
- export declare function getField(id: string): GetFieldResponse;
7
+ export declare function getField(key: string): GetFieldResponse;
8
+ export {};
@@ -1 +1 @@
1
- import{getPicked as s}from"./client.js";function r(e){const t=s(),n=t.fields.getById(e),o=t.responses.getByFieldId(e);return{field:n,value:o}}export{r as getField};
1
+ import{getPicked as s}from"./client.js";function r(e){const t=s(),n=t.fields.getByKey(e),o=t.responses.getByFieldKey(e);return{field:n,value:o}}export{r as getField};
@@ -1,13 +1,13 @@
1
1
  import { Store } from 'effector';
2
- import { ExperienceStep } from '@picked/client';
3
- import { FormInstance, FormField } from '../form/create-form.svelte';
4
- export interface StepContext extends FormInstance {
2
+ import { ExperienceStep, Field, Response } from '@picked/client';
3
+ type FieldWithValue = Field & {
4
+ value: Response | undefined;
5
+ };
6
+ export interface StepContext {
5
7
  step: Store<ExperienceStep>;
6
- /** Directly accessible fields with current response values — usable without a form */
7
- fields: Store<FormField[]>;
8
- isActive: Store<boolean>;
9
- /** @deprecated Use `isActive` instead */
8
+ fields: Store<FieldWithValue[]>;
10
9
  isActiveStep: Store<boolean>;
11
10
  }
12
- export declare function setStepContext(stepId: string): StepContext;
13
11
  export declare function getStep(): StepContext;
12
+ export declare function setStepContext(stepId: string): StepContext;
13
+ export {};
@@ -1 +1 @@
1
- import{getContext as h,onMount as A,setContext as E}from"svelte";import{createEffect as g,createEvent as O,sample as p}from"effector";import{STEP_CONTEXT_KEY as x}from"../context.js";import{createForm as k}from"../form/create-form.svelte";import{getStepRouter as C}from"../router/context.js";import{getPicked as I,getExperience as _}from"./client.js";function b(c){const o=_().treeStore.map(s=>{const d=s.steps.find(f=>f.id===c);if(!d)throw new Error(`Step with id "${c}" not found in the experience tree.`);return d}),l=o.map(s=>s.label),u=o.map(s=>s.fieldIds),r=k(c,l,u);return{stepStore:o,form:r}}function K(c){const{activeStep:a}=C(),o=a.map(e=>e?.id===c),{stepStore:l,form:u}=b(c),r=I(),s=g(e=>{r.fields.resolveAll(e)});function d(e,n){return e.dependsOn?.length?e.dependsOn.every(i=>{const t=n[i];return t!=null&&t!==""&&!(Array.isArray(t)&&t.length===0)}):!0}const f=O();p({clock:f,source:{step:l,fields:r.fields.listStore},fn:({step:e,fields:n})=>e.fieldIds.filter(i=>!n.find(t=>t.id===i)?.dependsOn?.length),target:s});const m=g(({step:e,fields:n,responses:i})=>{for(const t of e.fieldIds){const S=n.find(v=>v.id===t);S?.dependsOn?.length&&(d(S,i)?r.fields.resolveAll([t],{force:!0,context:{responses:i}}):r.fields.setWaiting(t))}});return p({clock:f,source:{step:l,fields:r.fields.listStore,responses:r.responses.getAllStore},target:m}),A(()=>{f()}),p({clock:o,source:{step:l,fields:r.fields.listStore},filter:(e,n)=>n,fn:({step:e,fields:n})=>e.fieldIds.filter(i=>!n.find(t=>t.id===i)?.dependsOn?.length),target:s}),p({clock:o,source:{step:l,fields:r.fields.listStore,responses:r.responses.getAllStore},filter:(e,n)=>n,target:m}),E(x,{step:l,isActive:o,isActiveStep:o,...u})}function M(){return h(x)}export{M as getStep,K as setStepContext};
1
+ import{getContext as g,setContext as y}from"svelte";import{createStore as v,combine as a,attach as h,createEffect as k,sample as l}from"effector";import{getStepFields as x}from"../utils/get-step-fields.js";import{STEP_CONTEXT_KEY as u}from"../context.js";import{getStepRouter as E}from"../router/context.js";import{getPicked as K}from"./client.js";function _(){return g(u)}function b(o){const r=K(),{activeStep:S}=E(),n=S.map(e=>e?.id===o),f=r.steps.listStore.map(e=>{const i=e.find(t=>(t.key??t.id)===o);if(!i)throw new Error(`Step with id "${o}" not found in the step registry.`);return i}),d=v([]),m=a({fields:d,responses:r.responses.listStore},({fields:e,responses:i})=>e.map(t=>{const s=i.find(p=>p.fieldKey===t.key||p.fieldId===t.id)?.value;return{...t,value:s}})),c=h({source:a({step:f,fields:r.fields.listStore}),effect:k(async({step:e,fields:i})=>{const t=e.fieldKeys&&e.fieldKeys.length>0?i.filter(s=>(e.fieldKeys??[]).includes(s.key)):i.filter(s=>(e.fieldIds??[]).includes(s.id));return await x(r,t)})});return l({source:{isActiveStep:n},filter:({isActiveStep:e})=>e,target:c}),l({clock:r.responses.listStore,source:{isActiveStep:n},filter:({isActiveStep:e})=>e,target:c}),l({source:c.doneData,target:d}),y(u,{step:f,fields:m,isActiveStep:n})}export{_ as getStep,b as setStepContext};
@@ -1,9 +1,13 @@
1
1
  <script lang="ts">
2
2
  import type { ExperienceInstance } from '@picked/client'
3
3
  import { Button } from '../ui'
4
+ import { getPicked } from '../../../hooks'
4
5
 
5
6
  export let experienceInstance: ExperienceInstance
6
7
 
8
+ const client = getPicked()
9
+ const experienceLookup = client.experiences.get(experienceInstance.experienceId)
10
+
7
11
  const currentNode = experienceInstance.currentNodeStore
8
12
  const currentStep = experienceInstance.currentStepStore
9
13
  const nextStepNodes = experienceInstance.nextReachableStepNodesStore
@@ -12,15 +16,24 @@
12
16
  const isOpen = experienceInstance.isOpenStore
13
17
 
14
18
  $: totalNextStepNodes = $nextStepNodes.length
19
+ $: experienceName = $experienceLookup?.name ?? $experienceLookup?.key ?? experienceInstance.experienceId
20
+ $: currentNodeDisplay = $currentNode
21
+ ? (('name' in $currentNode && $currentNode.name) ||
22
+ ('stepKey' in $currentNode && $currentNode.stepKey) ||
23
+ $currentNode.id)
24
+ : ''
25
+ $: currentStepDisplay = $currentStep
26
+ ? ($currentStep.name ?? $currentStep.key ?? $currentStep.id)
27
+ : ''
15
28
  </script>
16
29
 
17
30
  <tr>
18
- <td>{experienceInstance.experienceId}</td>
31
+ <td>{experienceName}</td>
19
32
  <td><Button variant="ghost" size="small" onclick={() => experienceInstance.toggleOpen()}>{$isOpen}</Button></td>
20
- <td>{$currentNode?.id || ''}</td>
33
+ <td>{currentNodeDisplay}</td>
21
34
  <td>{$currentNode?.type || ''}</td>
22
- <td>{$currentStep?.id || ''}</td>
23
- <td>{$currentStep?.label || ''}</td>
35
+ <td>{currentStepDisplay}</td>
36
+ <td>{$currentStep?.title || ''}</td>
24
37
  <td>{totalNextStepNodes}</td>
25
38
  <td>{$stepsToOrigin[0]} - {$stepsToOrigin[1]}</td>
26
39
  <td>{$stepsToLeaf[0]} - {$stepsToLeaf[1]}</td>
@@ -6,7 +6,7 @@
6
6
  export let experienceInstances: ExperienceInstance[]
7
7
 
8
8
  const columns = [
9
- { key: 'experience', header: 'Experience' },
9
+ { key: 'experience', header: 'Name' },
10
10
  { key: 'isOpen', header: 'Is Open' },
11
11
  { key: 'currentNodeId', header: 'Current node ID' },
12
12
  { key: 'currentNodeType', header: 'Current node Type' },
@@ -1,11 +1,23 @@
1
1
  <script lang="ts">
2
2
  import type { EventBusPayload, EventHistoryEntry } from '@picked/client'
3
3
  import { Badge, Card, CodeBlock, Stack, Text } from '../../ui'
4
+ import { getPicked } from '../../../../hooks'
4
5
 
5
6
  export let event: EventHistoryEntry
6
7
  export let isExpanded: boolean
7
8
  export let onToggle: () => void
8
9
 
10
+ const client = getPicked()
11
+
12
+ const eventExpId =
13
+ 'experience' in event.payload && event.payload.experience
14
+ ? event.payload.experience.experienceId
15
+ : undefined
16
+ const eventExpLookup = client.experiences.get(eventExpId ?? '')
17
+ $: eventExpName = eventExpId
18
+ ? ($eventExpLookup?.name ?? $eventExpLookup?.key ?? eventExpId)
19
+ : '-'
20
+
9
21
  function formatTime(date: Date): string {
10
22
  return date.toLocaleTimeString('en-US', {
11
23
  hour12: false,
@@ -21,59 +33,49 @@
21
33
  value: unknown
22
34
  }
23
35
 
24
- function getPayloadFields(payload: EventBusPayload): PayloadField[] {
36
+ function getExperienceFields(exp: unknown): PayloadField[] {
37
+ if (!exp || typeof exp !== 'object') return []
25
38
  const fields: PayloadField[] = []
39
+ if ('id' in exp && exp.id) fields.push({ label: 'Experience ID', value: exp.id })
40
+ if ('experienceId' in exp && exp.experienceId)
41
+ fields.push({ label: 'Experience Type', value: exp.experienceId })
42
+ return fields
43
+ }
26
44
 
27
- fields.push({ label: 'Type', value: payload.type })
45
+ function getStepFields(step: unknown): PayloadField[] {
46
+ if (!step || typeof step !== 'object') return []
47
+ const fields: PayloadField[] = []
48
+ if ('id' in step && step.id) fields.push({ label: 'Step ID', value: step.id })
49
+ // eslint-disable-next-line sonarjs/deprecation -- backwards-compat fallback for label
50
+ const stepName = ('name' in step && step.name) || ('label' in step && step.label)
51
+ if (stepName) fields.push({ label: 'Step Name', value: stepName })
52
+ return fields
53
+ }
28
54
 
29
- if ('value' in payload && payload.value !== undefined) {
55
+ function getScalarFields(payload: EventBusPayload): PayloadField[] {
56
+ const fields: PayloadField[] = []
57
+ if ('value' in payload && payload.value !== undefined)
30
58
  fields.push({ label: 'Value', value: payload.value })
31
- }
32
-
33
- if ('experience' in payload && payload.experience) {
34
- const exp = payload.experience
35
- if (exp && typeof exp === 'object') {
36
- if ('id' in exp && exp.id) {
37
- fields.push({ label: 'Experience ID', value: exp.id })
38
- }
39
- if ('experienceId' in exp && exp.experienceId) {
40
- fields.push({ label: 'Experience Type', value: exp.experienceId })
41
- }
42
- }
43
- }
44
-
45
- if ('step' in payload && payload.step) {
46
- const step = payload.step
47
- if (step && typeof step === 'object') {
48
- if ('id' in step && step.id) {
49
- fields.push({ label: 'Step ID', value: step.id })
50
- }
51
- if ('label' in step && step.label) {
52
- fields.push({ label: 'Step Label', value: step.label })
53
- }
54
- }
55
- }
56
-
57
- if ('form' in payload && payload.form) {
59
+ if ('form' in payload && payload.form)
58
60
  fields.push({ label: 'Form', value: JSON.stringify(payload.form) })
59
- }
60
-
61
- if ('feature' in payload && payload.feature) {
61
+ if ('feature' in payload && payload.feature)
62
62
  fields.push({ label: 'Feature', value: JSON.stringify(payload.feature) })
63
- }
64
-
65
- if ('exception' in payload && payload.exception) {
63
+ if ('exception' in payload && payload.exception)
66
64
  fields.push({ label: 'Exception', value: JSON.stringify(payload.exception) })
67
- }
68
-
69
- if ('version' in payload && payload.version) {
65
+ if ('version' in payload && payload.version)
70
66
  fields.push({ label: 'Version', value: payload.version })
71
- }
67
+ return fields
68
+ }
72
69
 
73
- if ('id' in payload && payload.id && payload.type === 'session') {
70
+ function getPayloadFields(payload: EventBusPayload): PayloadField[] {
71
+ const fields: PayloadField[] = [{ label: 'Type', value: payload.type }]
72
+ fields.push(...getScalarFields(payload))
73
+ if ('experience' in payload && payload.experience)
74
+ fields.push(...getExperienceFields(payload.experience))
75
+ if ('step' in payload && payload.step)
76
+ fields.push(...getStepFields(payload.step))
77
+ if ('id' in payload && payload.id && payload.type === 'session')
74
78
  fields.push({ label: 'Session ID', value: payload.id })
75
- }
76
-
77
79
  return fields
78
80
  }
79
81
 
@@ -97,9 +99,7 @@
97
99
  <Badge status="info">{event.payload.type}</Badge>
98
100
  </td>
99
101
  <td>
100
- <Text size="small">
101
- {'experience' in event.payload && event.payload.experience ? event.payload.experience.experienceId : '-'}
102
- </Text>
102
+ <Text size="small">{eventExpName}</Text>
103
103
  </td>
104
104
  <td>
105
105
  <Text size="caption" color="subdued">
@@ -114,7 +114,7 @@
114
114
  <Text size="small" weight="semibold">Event Payload</Text>
115
115
  <Card>
116
116
  <Stack direction="vertical" gap="small">
117
- {#each getPayloadFields(event.payload) as field}
117
+ {#each getPayloadFields(event.payload) as field (field.label)}
118
118
  <Text size="caption" inline>
119
119
  <Text size="caption" weight="semibold" color="subdued" inline>{field.label}:</Text>
120
120
  <CodeBlock code={formatValue(field.value)} inline />
@@ -11,7 +11,7 @@
11
11
 
12
12
  <Stack direction="vertical" gap="medium" padding="medium">
13
13
  <Stack direction="horizontal" gap="small">
14
- <Button variant="primary" size="small" onclick={() => client.launchExperienceById(experience.id)}>
14
+ <Button variant="primary" size="small" onclick={() => client.experiences.open({ id: experience.id, options: { bypassCondition: true } })}>
15
15
  Launch experience
16
16
  </Button>
17
17
  </Stack>
@@ -9,17 +9,16 @@
9
9
  let zoom = 1
10
10
  let selectedNode = null
11
11
  let highlightedPath: string[][] = []
12
- let error: string | null = null
12
+ let error = null
13
13
  const loading = false
14
14
 
15
15
  let treeRenderingDone = false
16
16
 
17
17
  const tree = experience.treeStore
18
18
 
19
- $: steps = $tree.steps
20
19
  $: nodes = $tree.nodes
21
20
  $: edges = $tree.edges
22
- $: conditionals = $tree.conditionals
21
+ $: conditions = $tree.conditions
23
22
 
24
23
  let zoomBehavior: d3.ZoomBehavior<SVGSVGElement, unknown>
25
24
 
@@ -89,8 +88,7 @@
89
88
  function renderGraph() {
90
89
  d3.select(svgEl).selectAll('*').remove()
91
90
 
92
- const stepsMap = new Map(steps.map((s) => [s.id, s]))
93
- const conditionalsMap = new Map(conditionals.map((c) => [c.id, c]))
91
+ const conditionsMap = new Map(conditions.map((c) => [c.id, c]))
94
92
  const outgoingEdges = new Map<string, ExperienceEdge[]>()
95
93
  edges.forEach((e) => {
96
94
  if (!outgoingEdges.has(e.sourceNodeId)) outgoingEdges.set(e.sourceNodeId, [])
@@ -99,12 +97,13 @@
99
97
 
100
98
  const getNodeLabel = (node: ExperienceNode): string => {
101
99
  if (node.type === 'origin') return 'Start'
102
- if (node.type === 'step' && node.stepId)
103
- return stepsMap.get(node.stepId)?.label ?? 'Unknown Step'
104
- if (node.type === 'conditional' && node.conditionalId) {
105
- const conditional = conditionalsMap.get(node.conditionalId)
106
- if (conditional?.label) return conditional.label
107
- if (conditional?.condition) return formatLogicRule(conditional.condition)
100
+ if (node.type === 'step')
101
+ return node.name ?? node.stepKey ?? node.stepId ?? 'Unknown Step'
102
+ if (node.type === 'conditional') {
103
+ const conditionId = node.conditionId ?? node.conditionalId
104
+ const conditionItem = conditionId ? conditionsMap.get(conditionId) : undefined
105
+ if (conditionItem?.label) return conditionItem.label
106
+ if (conditionItem?.condition) return formatLogicRule(conditionItem.condition)
108
107
  return 'Condition'
109
108
  }
110
109
  return 'Unknown Node'
@@ -9,7 +9,7 @@
9
9
  $: tabs = $experiences.map((experience) => {
10
10
  return {
11
11
  id: experience.id,
12
- label: experience.id,
12
+ label: experience.name ?? experience.key ?? experience.id,
13
13
  component: ExperienceScreen,
14
14
  props: {
15
15
  experience,
@@ -1,12 +1,17 @@
1
1
  <script lang="ts">
2
2
  import type { HistoryEntry } from '@picked/client'
3
+ import { getPicked } from '../../../../hooks'
3
4
 
4
5
  export let historyEntry: HistoryEntry
6
+
7
+ const client = getPicked()
8
+ const experienceLookup = client.experiences.get(historyEntry.experienceId)
9
+ $: experienceName = $experienceLookup?.name ?? $experienceLookup?.key ?? historyEntry.experienceId
5
10
  </script>
6
11
 
7
12
  <tr>
8
13
  <td>{historyEntry.id}</td>
9
- <td>{historyEntry.experienceId}</td>
14
+ <td>{experienceName}</td>
10
15
  <td>{historyEntry.node.id}</td>
11
16
  <td>{historyEntry.node.type}</td>
12
17
  <td>{historyEntry.at}</td>
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import type { ResponseListItem, PrimitiveFieldValue, Field, FieldValue } from '@picked/client'
2
+ import type { ResponseListItem, PrimitiveFieldValue, Field } from '@picked/client'
3
3
  import { getPicked } from '../../../../hooks'
4
4
  import { Button, Text, CodeBlock, Banner, Stack } from '../../ui'
5
5
 
@@ -11,10 +11,9 @@
11
11
  let isEditing = false
12
12
  let editValue = ''
13
13
  let editError = ''
14
- let selectedOptions: Set<FieldValue> = new Set()
14
+ let selectedOptions: Set<PrimitiveFieldValue> = new Set()
15
15
 
16
- $: hasOptions = Array.isArray(field?.options) && field.options.length > 0
17
- $: staticOptions = Array.isArray(field?.options) ? field.options : null
16
+ $: hasOptions = field?.options && field.options.length > 0
18
17
  $: currentValue = response?.value ?? null
19
18
 
20
19
  function formatValue(value: unknown): string {
@@ -53,7 +52,7 @@
53
52
  selectedOptions = new Set()
54
53
  }
55
54
 
56
- function toggleOption(optionValue: FieldValue) {
55
+ function toggleOption(optionValue: PrimitiveFieldValue) {
57
56
  if (field?.multiple) {
58
57
  if (selectedOptions.has(optionValue)) {
59
58
  selectedOptions.delete(optionValue)
@@ -66,7 +65,7 @@
66
65
  }
67
66
  }
68
67
 
69
- function parseValue(input: string): FieldValue {
68
+ function parseValue(input: string): unknown {
70
69
  input = input.trim()
71
70
 
72
71
  if (input === '' || input === '-') {
@@ -100,14 +99,14 @@
100
99
 
101
100
  async function saveEdit() {
102
101
  try {
103
- let newValue: FieldValue
102
+ let newValue: unknown
104
103
 
105
104
  if (hasOptions) {
106
105
  const values = Array.from(selectedOptions)
107
106
  if (field?.multiple) {
108
107
  newValue = values
109
108
  } else {
110
- newValue = values.at(0) ?? null
109
+ newValue = values[0] ?? null
111
110
  }
112
111
  } else {
113
112
  newValue = parseValue(editValue)
@@ -135,7 +134,7 @@
135
134
 
136
135
  <tr>
137
136
  <td>
138
- <CodeBlock code={field.name} inline />
137
+ <CodeBlock code={field.key ?? field.id} inline />
139
138
  </td>
140
139
  <td>
141
140
  <Text size="small" weight="semibold">{field.label}</Text>
@@ -143,9 +142,9 @@
143
142
  <td>
144
143
  {#if isEditing}
145
144
  <Stack direction="vertical" gap="small">
146
- {#if hasOptions && staticOptions}
145
+ {#if hasOptions && field?.options}
147
146
  <Stack direction="vertical" gap="small">
148
- {#each staticOptions as option (option.id)}
147
+ {#each field.options as option (option.id)}
149
148
  <label class="option-label">
150
149
  {#if field.multiple}
151
150
  <input
@@ -34,7 +34,7 @@
34
34
  }
35
35
 
36
36
  const columns = [
37
- { key: 'id', header: 'ID' },
37
+ { key: 'id', header: 'Key' },
38
38
  { key: 'field', header: 'Field' },
39
39
  { key: 'value', header: 'Value' },
40
40
  { key: 'type', header: 'Type' },
@@ -13,6 +13,7 @@
13
13
 
14
14
  const step = {
15
15
  id,
16
+ key: id
16
17
  }
17
18
 
18
19
  const { registerStep, unregisterStep } = getStepRouter()
@@ -51,7 +51,7 @@
51
51
 
52
52
  sample({
53
53
  source: { steps: stepsStore, currentStep: currentStepStore },
54
- fn: ({ steps, currentStep }) => steps.find((s) => s.id === currentStep?.id) || null,
54
+ fn: ({ steps, currentStep }) => steps.find((s) => s.id === (currentStep?.key ?? currentStep?.id)) || null,
55
55
  target: setActiveStepEvent,
56
56
  })
57
57
 
@@ -0,0 +1,2 @@
1
+ import { Field, Picked } from '@picked/client';
2
+ export declare function getStepFields(client: Picked, fields: Field[]): Promise<Field[]>;
@@ -0,0 +1 @@
1
+ import{evaluateRuleLogic as s}from"@picked/client";function u(o){return o.options?.some(n=>"condition"in n)??!1}function l(o){return o.some(n=>n.condition||n.conditionKey||u(n))}async function a(o,n){return n.conditionKey?o.conditions.getByKey(n.conditionKey).getState()?.condition??null:n.condition??null}async function g(o,n){return n.length===0||!l(n)?n:(await Promise.all(n.map(async t=>{const r=await a(o,t);if(!(r?await s(o,r):!0))return null;if(!t.options||!u(t))return t;const e=(await Promise.all(t.options.map(async i=>i.condition?await s(o,i.condition)?i:null:i))).filter(i=>i!==null);return e.length===t.options.length?t:e.length===0?null:{...t,options:e}}))).filter(t=>t!==null)}export{g as getStepFields};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@picked/svelte",
3
- "version": "0.0.1-dev.1773729274420",
3
+ "version": "0.0.1-dev.1773807602703",
4
4
  "dependencies": {
5
5
  "d3": "^7.9.0"
6
6
  },
@@ -88,7 +88,7 @@
88
88
  },
89
89
  "module": "./dist/index.js",
90
90
  "peerDependencies": {
91
- "@picked/client": "0.0.1-dev.1773729274420",
91
+ "@picked/client": "0.0.1-dev.1773807602703",
92
92
  "effector": "^23.3.0",
93
93
  "svelte": "^5.34.3"
94
94
  },
@@ -1,13 +0,0 @@
1
- import { FieldBase, FieldValue } from '@picked/client';
2
- type FieldLike = Pick<FieldBase, 'id' | 'type' | 'multiple'>;
3
- /**
4
- * Parses a FormData snapshot into a typed record of field responses.
5
- *
6
- * - Fields absent from FormData are mapped to `null`.
7
- * - Empty string values are mapped to `null`.
8
- * - Repeated entries for the same field (e.g. multi-select checkboxes) are
9
- * merged into a typed array — matching the field's declared element type.
10
- * - Values are coerced to the field's declared type (text → string, number → number, boolean → boolean).
11
- */
12
- export declare function buildResponsesFromFormData(fields: FieldLike[], formData: FormData): Record<string, FieldValue>;
13
- export {};
@@ -1 +0,0 @@
1
- function i(e,o,n){const r=e[o];if(r===void 0){e[o]=n;return}if(Array.isArray(r)&&Array.isArray(n)&&n.length>0){typeof r[0]=="string"?e[o]=[...r,...n].map(String):typeof r[0]=="number"?e[o]=[...r,...n].map(Number):typeof r[0]=="boolean"?e[o]=[...r,...n].map(t=>!!t):e[o]=n;return}e[o]=n}function b(e,o){const n=Array.from(o.entries()),r={};return e.forEach(t=>{let m=n.filter(([s])=>s===t.id);m.length===0&&(m=[[t.id,""]]),m.forEach(([,s])=>{if(s===""){i(r,t.id,null);return}switch(t.type){case"text":case"object":i(r,t.id,t.multiple?[String(s)]:String(s));break;case"number":i(r,t.id,t.multiple?[Number(s)]:Number(s));break;case"boolean":i(r,t.id,t.multiple?[s==="true"]:s==="true");break}})}),r}export{b as buildResponsesFromFormData};
@@ -1,17 +0,0 @@
1
- import { FieldValue, FieldWithStatus, Picked } from '@picked/client';
2
- import { Store, StoreWritable } from 'effector';
3
- /**
4
- * Creates a reactive store of evaluated fields for a given set of field IDs.
5
- *
6
- * Fields are re-evaluated whenever:
7
- * - The available field IDs change (e.g. a step is entered)
8
- * - Any response is committed (options may depend on prior answers)
9
- * - The global field registry changes (fields registered after init)
10
- * - Staged values change (field visibility conditions may reference responses.*)
11
- *
12
- * Evaluation runs `getStepFields` with effective responses (committed + staged) as context,
13
- * so visibility conditions see live in-session values rather than only committed responses.
14
- */
15
- export declare function createFieldEvaluator(client: Picked, fieldIds: Store<string[]>, stagedValuesStore: StoreWritable<Record<string, FieldValue>>): {
16
- fieldsStore: Store<FieldWithStatus[]>;
17
- };
@@ -1 +0,0 @@
1
- import{getStepFields as m}from"./get-step-fields.js";import{createStore as u,attach as F,createEffect as S,sample as i}from"effector";function k(e,r,s){const o=u([]),t=F({source:{allFields:e.fields.listStore,fieldIds:r,staged:s,committed:e.responses.getAllStore},effect:S(async({allFields:l,fieldIds:a,staged:c,committed:d})=>{const f=l.filter(p=>a.includes(p.id)),n={...d,...c};return await m(e,f,{responses:n})})});return i({clock:t.doneData,target:o}),t(),i({clock:[e.responses.listStore,r,e.fields.listStore,s],target:t}),{fieldsStore:o}}export{k as createFieldEvaluator};
@@ -1,41 +0,0 @@
1
- import { FieldValue, FieldWithStatus, Picked, ValidationError } from '@picked/client';
2
- import { EventCallable, Store, StoreWritable } from 'effector';
3
- export interface FieldValidation {
4
- /** Dispatch when a field value changes — runs validation if the field has been touched. */
5
- fieldChangeEvent: EventCallable<{
6
- fieldId: string;
7
- value: FieldValue;
8
- }>;
9
- /** Dispatch when a field loses focus — always runs full validation including required check. */
10
- fieldBlurEvent: EventCallable<{
11
- fieldId: string;
12
- value: FieldValue;
13
- }>;
14
- /** Which fields have been interacted with — used by formFieldsStore to show/hide errors. */
15
- touchedStore: StoreWritable<Partial<Record<string, boolean>>>;
16
- /**
17
- * Per-field validation errors — written by client-side validation and overwritten
18
- * by server errors on commit failure. Exposed as writable so the submit pipeline
19
- * can populate server errors and reset state on new submit attempts.
20
- */
21
- fieldErrorsStore: StoreWritable<Partial<Record<string, ValidationError>>>;
22
- /** Flat list of all current validation errors — derived from fieldErrorsStore. */
23
- formErrorsStore: Store<ValidationError[]>;
24
- /**
25
- * Current in-session field values — updated on every onChange/onBlur before commit.
26
- * Used as the responses context for condition evaluation so conditions react to
27
- * live input without writing to client.responses prematurely.
28
- */
29
- stagedValuesStore: StoreWritable<Record<string, FieldValue>>;
30
- }
31
- /**
32
- * Creates the client-side validation slice for a form.
33
- *
34
- * Responsibilities:
35
- * - Tracks which fields have been touched (first blur = touched)
36
- * - Validates on blur (full: includes required check) and on change (if already touched)
37
- * - Uses the same json-logic evaluator the server uses, so client and server results match
38
- * - Maintains stagedValuesStore: current in-session values used as responses context for
39
- * condition evaluation, avoiding premature writes to client.responses
40
- */
41
- export declare function createFieldValidation(client: Picked, fieldsStore: Store<FieldWithStatus[]>, stagedValuesStore: StoreWritable<Record<string, FieldValue>>): FieldValidation;
@@ -1 +0,0 @@
1
- import{evaluateRuleLogic as M}from"@picked/client";import{createEvent as h,createStore as y,sample as p,attach as q}from"effector";function F(s){return s===null||s===""||Array.isArray(s)&&s.length===0}function R(s,k,c){const f=h(),a=h(),u=y({});p({clock:a,source:u,fn:(r,{fieldId:e})=>({...r,[e]:!0}),target:u});const g=y({}),v=g.map(r=>Object.values(r).filter(e=>e!==void 0)),m=q({source:{fields:k,staged:c,committed:s.responses.getAllStore},effect:async({fields:r,staged:e,committed:n},{fieldId:t,value:i,checkRequired:E})=>{const o=r.find(d=>d.id===t);if(!o)return{fieldId:t,error:null};if(o.validation&&Object.keys(o.validation).length>0){const d={...n,...e},l=await M(s,o.validation,{responses:d,$input:i});if(typeof l=="string")return{fieldId:t,error:{fieldId:t,userMessage:l,systemMessage:l,value:i}};if(l===!1)return{fieldId:t,error:{fieldId:t,userMessage:"This field is invalid",systemMessage:"Field validation rule returned false.",value:i}}}return E&&o.required&&F(i)?{fieldId:t,error:{fieldId:t,userMessage:"This field is required",systemMessage:"Field is required.",value:i}}:{fieldId:t,error:null}}});return p({clock:f,source:u,filter:(r,{fieldId:e})=>r[e]===!0,fn:(r,{fieldId:e,value:n})=>({fieldId:e,value:n,checkRequired:!1}),target:m}),p({clock:a,fn:({fieldId:r,value:e})=>({fieldId:r,value:e,checkRequired:!0}),target:m}),c.on(f,(r,{fieldId:e,value:n})=>({...r,[e]:n})),c.on(a,(r,{fieldId:e,value:n})=>({...r,[e]:n})),g.on(m.doneData,(r,{fieldId:e,error:n})=>{if(n===null){const t={...r};return delete t[e],t}return{...r,[e]:n}}),{fieldChangeEvent:f,fieldBlurEvent:a,touchedStore:u,fieldErrorsStore:g,formErrorsStore:v,stagedValuesStore:c}}export{R as createFieldValidation};
@@ -1 +0,0 @@
1
- import"svelte/internal/client";import{createStore as C,combine as P,attach as j,sample as u}from"effector";import{getPicked as N,getExperience as R}from"../hooks/client.js";import{createFieldEvaluator as W}from"./create-field-evaluator.js";import{createFieldValidation as q}from"./create-field-validation.js";import{createSubmitPipeline as w}from"./create-submit-pipeline.svelte";function x(m){const t=m.currentTarget;if(t instanceof HTMLInputElement)return t.type==="checkbox"?t.checked:t.value;if(t instanceof HTMLSelectElement||t instanceof HTMLTextAreaElement)return t.value}function X(m,t,k){const r=N(),g=R(),{currentStepStore:F,goToNextStep:I}=g,d=C({}),{fieldsStore:a}=W(r,k,d),{fieldChangeEvent:E,fieldBlurEvent:T,touchedStore:b,fieldErrorsStore:p,formErrorsStore:A}=q(r,a,d),y=P({fields:a,responses:r.responses.listStore,staged:d,fieldErrors:p,touched:b},({fields:s,responses:o,staged:i,fieldErrors:S,touched:f})=>s.map(e=>({...e,value:i[e.id]??o.find(n=>n.fieldId===e.id)?.value,error:S[e.id],touched:f[e.id]??!1,onChange:n=>{const c=x(n);c!==void 0&&E({fieldId:e.id,value:c})},onBlur:n=>{const c=x(n);c!==void 0&&T({fieldId:e.id,value:c})}}))),D=j({source:{staged:d,committed:r.responses.getAllStore,allFields:r.fields.listStore},effect:({staged:s,committed:o,allFields:i},S)=>{const f={...o,...s};for(const e of S){const n=i.find(v=>v.id===e);if(!n)continue;(n.dependsOn??[]).every(v=>{const l=f[v];return l!=null&&l!==""&&!(Array.isArray(l)&&l.length===0)})?r.fields.resolveAll([e],{force:!0,context:{responses:f}}):r.fields.setWaiting(e)}}});u({clock:E,source:{fields:a},fn:({fields:s},{fieldId:o})=>s.filter(i=>i.dependsOn?.includes(o)).map(i=>i.id),target:D});const{submitEvent:H,isFormSubmitting:L,onSubmit:M,onSuccess:O,onError:V,submitStarted:h,serverErrorsReceived:B}=w({client:r,experienceInstance:g,formId:m,title:t,stepStore:F,fieldsStore:a,goToNextStep:I});return u({clock:h,fn:()=>({}),target:b}),u({clock:h,fn:()=>({}),target:p}),u({clock:B,fn:s=>Object.fromEntries(s.map(o=>[o.fieldId,o])),target:p}),{form:{onsubmit:H,errors:A,submitting:L,onSubmit:M,onSuccess:O,onError:V},fields:y}}export{X as createForm};
@@ -1,41 +0,0 @@
1
- import { FieldValue, FieldWithStatus, ValidationError } from '@picked/client';
2
- import { EventCallable, Store } from 'effector';
3
- import { ErrorCallback, SubmitCallback, SubmitEventWithCurrentTarget, SuccessCallback } from './create-submit-pipeline.svelte';
4
- export type FormField = Omit<FieldWithStatus, 'error'> & {
5
- value: FieldValue | undefined;
6
- /** Validation error for this field, or undefined if valid. */
7
- error: ValidationError | undefined;
8
- touched: boolean;
9
- onChange: (e: Event) => void;
10
- onBlur: (e: Event) => void;
11
- };
12
- /**
13
- * Return type for createForm()
14
- * Provides form handlers, reactive stores, and callback registration
15
- */
16
- export interface FormInstance<TFieldIds extends string = string> {
17
- /** Form element props (bind to <form>) */
18
- form: {
19
- onsubmit: EventCallable<SubmitEventWithCurrentTarget>;
20
- /** Flat list of all current validation errors — derived from fieldErrors */
21
- errors: Store<ValidationError[]>;
22
- /** Reactive store indicating if form is currently submitting */
23
- submitting: Store<boolean>;
24
- /** Register callbacks to transform/validate data before submission */
25
- onSubmit: (callback: SubmitCallback<TFieldIds>) => void;
26
- /** Register callbacks to run on successful form submission */
27
- onSuccess: (callback: SuccessCallback<TFieldIds>) => void;
28
- /** Register callbacks to run on form validation errors */
29
- onError: (callback: ErrorCallback) => void;
30
- };
31
- /** Reactive store of fields with current values, errors, touched state, and event handlers */
32
- fields: Store<FormField[]>;
33
- }
34
- /**
35
- * Creates a form instance tied to a set of field IDs.
36
- * Handles field evaluation, form submission, data transformation,
37
- * validation, event bus tracking, and step navigation.
38
- *
39
- * Used internally by setStepContext — access via getStep() in components.
40
- */
41
- export declare function createForm(formId: string, title: Store<string>, fieldIds: Store<string[]>): FormInstance;
@@ -1 +0,0 @@
1
- export * from './create-form.js';
@@ -1 +0,0 @@
1
- import*as C from"svelte/internal/client";import{createEvent as i,createStore as B,createEffect as l,attach as O,sample as s,split as R}from"effector";import{buildResponsesFromFormData as T}from"./build-responses.js";function k(c){let o=[];return{executeFx:l(async(...a)=>await c(o,...a)),register:a=>{const f=()=>{o=o.filter(p=>p!==a)};C.user_effect(()=>(o.push(a),f))}}}function q({client:c,experienceInstance:o,formId:u,title:m,stepStore:a,fieldsStore:f,goToNextStep:p}){const n=i(),y=i(),v=i(),_=i(),x=i(),d=B(!1),D=B(!1);D.on(_,()=>!0).on(n,()=>!1);const N=n.map(()=>{}),g=k(async(e,t)=>{await Promise.all(e.map(r=>Promise.resolve(r(t))))}),b=k(async(e,t)=>{await Promise.all(e.map(r=>Promise.resolve(r(t))))}),F=k(async(e,t)=>{let r=t;for(const E of e){const w=await Promise.resolve(E(r));w!==void 0&&(r=w)}return r}),P=l(async e=>{const t=await c.responses.commit(e);return{data:e,errors:t.errors}}),h=O({source:{step:a,title:m},mapParams:(e,t)=>({step:t.step,title:t.title,data:e.data}),effect:l(async({step:e,title:t,data:r})=>(c.eventBus({type:"form_submit_success",form:{id:u,title:t},experience:o,step:e??void 0}),r))}),S=O({source:{step:a,title:m},mapParams:(e,t)=>({step:t.step,title:t.title,errors:e.errors}),effect:l(async({step:e,title:t,errors:r})=>(c.eventBus({type:"form_submit_error",form:{id:u,title:t},exception:{reason:r},experience:o,step:e??void 0}),r.forEach(E=>{c.eventBus({type:"field_validation_error",exception:{reason:[E]},experience:o,step:e??void 0})}),r))});return s({clock:n,fn:()=>!0,target:d}),s({clock:n,source:{step:a,title:m},fn:({step:e,title:t})=>({formId:u,title:t,step:e}),target:l(({formId:e,title:t,step:r})=>{c.eventBus({type:"form_submit",form:{id:e,title:t},experience:o,step:r??void 0})})}),s({clock:n,source:{stepFields:f},fn:({stepFields:e},t)=>(t.preventDefault(),T(e,new FormData(t.currentTarget))),target:F.executeFx}),s({clock:F.executeFx.doneData,target:P}),R({source:P.doneData,match:{hasErrors:({errors:e})=>e.length>0},cases:{hasErrors:S,__:h}}),s({clock:h.doneData,target:y}),s({clock:y,fn:e=>({data:e,stopNavigation:_}),target:g.executeFx}),s({clock:g.executeFx.done,target:x}),s({clock:x,source:D,filter:e=>!e,target:p}),s({clock:S.doneData,target:v}),s({clock:v,target:b.executeFx}),s({clock:[x,b.executeFx.done],fn:()=>!1,target:d}),{submitEvent:n,isFormSubmitting:d,onSubmit:F.register,onSuccess:g.register,onError:b.register,submitStarted:N,serverErrorsReceived:v}}export{q as createSubmitPipeline};
@@ -1,53 +0,0 @@
1
- import { ExperienceInstance, ExperienceStep, FieldValue, FieldWithStatus, Picked, ValidationError } from '@picked/client';
2
- import { Event, EventCallable, Store } from 'effector';
3
- export type SubmitEventWithCurrentTarget = SubmitEvent & {
4
- currentTarget: EventTarget & HTMLFormElement;
5
- };
6
- export type SuccessCallback<TFieldIds extends string = string> = (options: {
7
- data: Record<TFieldIds, FieldValue>;
8
- stopNavigation: EventCallable<void>;
9
- }) => void | Promise<void>;
10
- export type ErrorCallback = (errors: ValidationError[]) => void | Promise<void>;
11
- export type SubmitCallback<TFieldIds extends string = string> = (data: Record<TFieldIds, FieldValue>) => Record<TFieldIds, FieldValue> | void | Promise<Record<TFieldIds, FieldValue> | void>;
12
- export interface SubmitPipelineConfig {
13
- client: Picked;
14
- experienceInstance: ExperienceInstance;
15
- formId: string;
16
- title: Store<string>;
17
- stepStore: Store<ExperienceStep | null>;
18
- fieldsStore: Store<FieldWithStatus[]>;
19
- goToNextStep: EventCallable<void>;
20
- }
21
- export interface SubmitPipeline {
22
- submitEvent: EventCallable<SubmitEventWithCurrentTarget>;
23
- isFormSubmitting: Store<boolean>;
24
- onSubmit: (callback: SubmitCallback) => void;
25
- onSuccess: (callback: SuccessCallback) => void;
26
- onError: (callback: ErrorCallback) => void;
27
- /**
28
- * Fired when a submit attempt starts — callers use this to clear validation state.
29
- * Separating this from `submitEvent` keeps the pipeline decoupled from the
30
- * validation module's internal stores.
31
- */
32
- submitStarted: Event<void>;
33
- /**
34
- * Fired when the server returns validation errors — callers write these to
35
- * `fieldErrorsStore` in the validation module.
36
- */
37
- serverErrorsReceived: Event<ValidationError[]>;
38
- }
39
- /**
40
- * Creates the submit pipeline for a form instance.
41
- *
42
- * Manages the full lifecycle of a form submission:
43
- * - Emitting event bus calls (form_submit, form_submit_success, form_submit_error)
44
- * - Running onSubmit data-transform callbacks in series
45
- * - Committing responses to the server
46
- * - Running onSuccess/onError callbacks in parallel
47
- * - Navigating to the next step (unless stopNavigation is called)
48
- *
49
- * Returns coordination events (`submitStarted`, `serverErrorsReceived`) that the
50
- * caller wires to the validation module to keep validation state in sync without
51
- * the pipeline needing direct access to validation stores.
52
- */
53
- export declare function createSubmitPipeline({ client, experienceInstance, formId, title, stepStore, fieldsStore, goToNextStep, }: SubmitPipelineConfig): SubmitPipeline;
@@ -1 +0,0 @@
1
- export * from './create-submit-pipeline.js';
@@ -1,16 +0,0 @@
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 {};
@@ -1 +0,0 @@
1
- import{createEvent as _,createEffect as f,attach as v,sample as y,split as k}from"effector";import{getPicked as D,getExperience as F}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:m,title:l,onError:b,onSuccess:g}){const a=D(),x=B(),i=F(),{currentStepStore:u}=i,d=_(),h=f(t=>a.responses.commit(t)),E=v({source:{step:u},effect:f(({step:t})=>{a.eventBus({type:"form_submit_success",form:{id:m,title:l},experience:i,step:t??void 0}),g?.()})}),S=v({source:{step:u},mapParams:(t,s)=>({step:s.step,errors:t.errors}),effect:f(({step:t,errors:s})=>{a.eventBus({type:"form_submit_error",form:{id:m,title:l},exception:{reason:s},experience:i,step:t??void 0}),s.forEach(c=>{a.eventBus({type:"field_validation_error",exception:{reason:[c]},experience:i,step:t??void 0})}),b?.(s)})});return y({clock:d,source:{step:u},fn:({step:t})=>({formId:m,title:l,step:t}),target:f(({formId:t,title:s,step:c})=>{a.eventBus({type:"form_submit",form:{id:t,title:s},experience:i,step:c??void 0})})}),y({clock:d,source:{fields:a.fields.listStore,stepFields:x.fields,step:u},fn:({stepFields:t},s)=>{s.preventDefault();const c=new FormData(s.currentTarget),A=Array.from(c.entries()),p={data:{},push(r){const o=r.questionId,e=this.data[o];e?Array.isArray(e)&&Array.isArray(r.value)?Array.isArray(e)&&Array.isArray(r.value)&&(typeof e[0]=="string"?this.data[o]=[...e,...r.value].map(String):typeof e[0]=="number"?this.data[o]=[...e,...r.value].map(Number):typeof e[0]=="boolean"?this.data[o]=[...e,...r.value].map(n=>!!n):this.data[o]=r.value):this.data[o]=r.value:this.data[o]=r.value}};return t.forEach(r=>{const o=A.filter(([e])=>e===r.id);o.length!==0&&o.forEach(([,e])=>{const n=r.id;if(e===""){p.push({questionId:n,value:null});return}switch(r.type){case"text":p.push({questionId:n,value:r.multiple?[String(e)]:String(e)});break;case"number":p.push({questionId:n,value:r.multiple?[Number(e)]:Number(e)});break;case"boolean":p.push({questionId:n,value:r.multiple?[e==="true"]:e==="true"});break}})}),p.data},target:h}),k({source:h.doneData,match:{hasErrors:({errors:t})=>t.length>0},cases:{hasErrors:S,__:E}}),{form:{onsubmit:d}}}export{z as getForm};
@@ -1,2 +0,0 @@
1
- import { Field, Picked, evaluateRuleLogic } from '@picked/client';
2
- export declare function getStepFields<T extends Field>(client: Picked, fields: T[], context?: Parameters<typeof evaluateRuleLogic>[2]): Promise<T[]>;
@@ -1 +0,0 @@
1
- import{evaluateRuleLogic as e}from"@picked/client";function u(o){return Array.isArray(o.options)&&o.options.some(t=>"condition"in t)}function a(o){return o.some(t=>t.condition||u(t))}async function R(o,t,s){return t.length===0||!a(t)?t:(await Promise.all(t.map(async n=>{if(!(n.condition?await e(o,n.condition,s):!0))return null;if(!Array.isArray(n.options)||!u(n))return n;const r=(await Promise.all(n.options.map(async i=>i.condition?await e(o,i.condition,s)?i:null:i))).filter(i=>i!==null);return r.length===n.options.length?n:r.length===0?null:{...n,options:r}}))).filter(n=>n!==null)}export{R as getStepFields};