@picked/svelte 0.0.50-next.4 → 0.0.51-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ import{getExperience as e,getPicked as t}from"../hooks/client.js";import{createForm as n}from"@picked/client/form";import*as r from"svelte/internal/client";function i(i,a,o){let s=n({client:t(),experienceInstance:e()},i,a,o);return{...s,form:{...s.form,onBeforeSubmit:e=>{r.user_effect(()=>s.form.onBeforeSubmit(e))},onSuccess:e=>{r.user_effect(()=>s.form.onSuccess(e))},onError:e=>{r.user_effect(()=>s.form.onError(e))}}}}export{i as createForm};
@@ -0,0 +1,24 @@
1
+ import { Store } from 'effector';
2
+ import { FormInstance as CoreFormInstance, ErrorCallback, SubmitCallback, SuccessCallback } from '@picked/client/form';
3
+ export type { FormField } from '@picked/client/form';
4
+ /**
5
+ * Svelte FormInstance — identical to core but callback registration returns void
6
+ * (cleanup is handled automatically via $effect, not by the consumer).
7
+ */
8
+ export interface FormInstance<TFieldIds extends string = string> extends Omit<CoreFormInstance<TFieldIds>, 'form'> {
9
+ form: Omit<CoreFormInstance<TFieldIds>['form'], 'onBeforeSubmit' | 'onSuccess' | 'onError'> & {
10
+ onBeforeSubmit: (callback: SubmitCallback<TFieldIds>) => void;
11
+ onSuccess: (callback: SuccessCallback<TFieldIds>) => void;
12
+ onError: (callback: ErrorCallback) => void;
13
+ };
14
+ }
15
+ /**
16
+ * Creates a form instance with Svelte lifecycle management.
17
+ *
18
+ * Wraps the framework-agnostic createForm from @picked/client/form:
19
+ * - Injects client and experienceInstance from Svelte context
20
+ * - Auto-wires callback cleanup via $effect so consumers don't need to manage lifecycle
21
+ *
22
+ * Used internally by setStepContext — access via getStep() in components.
23
+ */
24
+ export declare function createForm(formId: string, title: Store<string>, fieldIds: Store<string[]>): FormInstance;
@@ -0,0 +1 @@
1
+ export * from './create-form.js';
@@ -0,0 +1,16 @@
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 {};
@@ -0,0 +1 @@
1
+ import{getExperience as e,getPicked as t}from"../hooks/client.js";import{getStep as n}from"../hooks/step.js";import"../hooks/index.js";import{buildResponsesFromFormData as r}from"@picked/client/form";import{attach as i,createEffect as a,createEvent as o,sample as s,split as c}from"effector";function l({id:l,title:u,onError:d,onSuccess:f}){let p=t(),m=n(),h=e(),{currentStepStore:g}=h,_=o(),v=a(e=>p.responses.commit(e)),y=i({source:{step:g},effect:a(({step:e})=>{p.eventBus({type:`form_submit_success`,form:{id:l,title:u},experience:h,step:e??void 0}),f?.()})}),b=i({source:{step:g},mapParams:(e,t)=>({step:t.step,errors:e.errors}),effect:a(({step:e,errors:t})=>{p.eventBus({type:`form_submit_error`,form:{id:l,title:u},exception:{reason:t},experience:h,step:e??void 0}),t.forEach(t=>{p.eventBus({type:`field_validation_error`,exception:{reason:[t]},experience:h,step:e??void 0})}),d?.(t)})});return s({clock:_,source:{step:g},fn:({step:e})=>({formId:l,title:u,step:e}),target:a(({formId:e,title:t,step:n})=>{p.eventBus({type:`form_submit`,form:{id:e,title:t},experience:h,step:n??void 0})})}),s({clock:_,source:{fields:p.fields.listStore,stepFields:m.fields,step:g},fn:({stepFields:e},t)=>(t.preventDefault(),r(e,new FormData(t.currentTarget))),target:v}),c({source:v.doneData,match:{hasErrors:({errors:e})=>e.length>0},cases:{hasErrors:b,__:y}}),{form:{onsubmit:_}}}export{l as getForm};
@@ -1,16 +1,2 @@
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
+ export type { FormField } from '@picked/client/form';
2
+ export { getForm } from './get-form';
@@ -1 +1 @@
1
- import{getExperience as e,getPicked as t}from"../hooks/client.js";import{getStep as n}from"../hooks/step.js";import"../hooks/index.js";import{attach as r,createEffect as i,createEvent as a,sample as o,split as s}from"effector";function c({id:c,title:l,onError:u,onSuccess:d}){let f=t(),p=n(),m=e(),{currentStepStore:h}=m,g=a(),_=i(e=>f.responses.commit(e)),v=r({source:{step:h},effect:i(({step:e})=>{f.eventBus({type:`form_submit_success`,form:{id:c,title:l},experience:m,step:e??void 0}),d?.()})}),y=r({source:{step:h},mapParams:(e,t)=>({step:t.step,errors:e.errors}),effect:i(({step:e,errors:t})=>{f.eventBus({type:`form_submit_error`,form:{id:c,title:l},exception:{reason:t},experience:m,step:e??void 0}),t.forEach(t=>{f.eventBus({type:`field_validation_error`,exception:{reason:[t]},experience:m,step:e??void 0})}),u?.(t)})});return o({clock:g,source:{step:h},fn:({step:e})=>({formId:c,title:l,step:e}),target:i(({formId:e,title:t,step:n})=>{f.eventBus({type:`form_submit`,form:{id:e,title:t},experience:m,step:n??void 0})})}),o({clock:g,source:{fields:f.fields.listStore,stepFields:p.fields,step:h},fn:({stepFields:e},t)=>{t.preventDefault();let n=new FormData(t.currentTarget),r=Array.from(n.entries()),i={data:{},push(e){let t=e.questionId,n=this.data[t];n&&Array.isArray(n)&&Array.isArray(e.value)?Array.isArray(n)&&Array.isArray(e.value)&&(typeof n[0]==`string`?this.data[t]=[...n,...e.value].map(String):typeof n[0]==`number`?this.data[t]=[...n,...e.value].map(Number):typeof n[0]==`boolean`?this.data[t]=[...n,...e.value].map(e=>!!e):this.data[t]=e.value):this.data[t]=e.value}};return e.forEach(e=>{let t=r.filter(([t])=>t===e.id);t.length===0&&(t=[[e.id,``]]),t.forEach(([,t])=>{let n=e.id;if(t===``){i.push({questionId:n,value:null});return}switch(e.type){case`text`:i.push({questionId:n,value:e.multiple?[String(t)]:String(t)});break;case`number`:i.push({questionId:n,value:e.multiple?[Number(t)]:Number(t)});break;case`boolean`:i.push({questionId:n,value:e.multiple?[t===`true`]:t===`true`});break}})}),i.data},target:_}),s({source:_.doneData,match:{hasErrors:({errors:e})=>e.length>0},cases:{hasErrors:y,__:v}}),{form:{onsubmit:g}}}export{c as getForm};
1
+ import{getForm as e}from"./get-form.js";export{e as getForm};
@@ -1,13 +1,13 @@
1
1
  import { Store } from 'effector';
2
- import { ExperienceStep, Field, Response } from '@picked/client';
3
- type FieldWithValue = Field & {
4
- value: Response | undefined;
5
- };
6
- export interface StepContext {
2
+ import { ExperienceStep } from '@picked/client';
3
+ import { FormInstance, FormField } from '../form/create-form.svelte';
4
+ export interface StepContext extends FormInstance {
7
5
  step: Store<ExperienceStep>;
8
- fields: Store<FieldWithValue[]>;
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 */
9
10
  isActiveStep: Store<boolean>;
10
11
  }
11
- export declare function getStep(): StepContext;
12
12
  export declare function setStepContext(stepId: string): StepContext;
13
- export {};
13
+ export declare function getStep(): StepContext;
@@ -1 +1 @@
1
- import{STEP_CONTEXT_KEY as e}from"../context.js";import{getExperience as t,getPicked as n}from"./client.js";import{getStepRouter as r}from"../router/context.js";import{attach as i,combine as a,createEffect as o,createStore as s,sample as c}from"effector";import{getContext as l,setContext as u}from"svelte";import{getStepFields as d}from"@picked/client";function f(){return l(e)}function p(l){let f=t(),p=n(),{activeStep:m}=r(),h=m.map(e=>e?.id===l),g=f.treeStore.map(e=>{let t=e.steps.find(e=>e.id===l);if(!t)throw Error(`Step with id "${l}" not found in the experience tree.`);return t}),_=s([]),v=a({fields:_,responses:p.responses.listStore},({fields:e,responses:t})=>e.map(e=>{let n=t.find(t=>t.fieldId===e.id)?.value;return{...e,value:n}})),y=i({source:a({step:g,fields:p.fields.listStore}),effect:o(async({step:{fieldIds:e},fields:t})=>await d(p,t.filter(t=>e.includes(t.id))))});return c({source:{isActiveStep:h},filter:({isActiveStep:e})=>e,target:y}),c({clock:p.responses.listStore,source:{isActiveStep:h},filter:({isActiveStep:e})=>e,target:y}),c({source:y.doneData,target:_}),u(e,{step:g,fields:v,isActiveStep:h})}export{f as getStep,p as setStepContext};
1
+ import{STEP_CONTEXT_KEY as e}from"../context.js";import{getExperience as t}from"./client.js";import{getStepRouter as n}from"../router/context.js";import{getContext as r,setContext as i}from"svelte";import{createForm as a}from"../form/create-form.svelte";function o(e){let n=t().treeStore.map(t=>{let n=t.steps.find(t=>t.id===e);if(!n)throw Error(`Step with id "${e}" not found in the experience tree.`);return n});return{stepStore:n,form:a(e,n.map(e=>e.label),n.map(e=>e.fieldIds))}}function s(t){let{activeStep:r}=n(),a=r.map(e=>e?.id===t),{stepStore:s,form:c}=o(t);return i(e,{step:s,isActive:a,isActiveStep:a,...c})}function c(){return r(e)}export{c as getStep,s as setStepContext};
@@ -20,7 +20,7 @@
20
20
  <td>{$currentNode?.id || ''}</td>
21
21
  <td>{$currentNode?.type || ''}</td>
22
22
  <td>{$currentStep?.id || ''}</td>
23
- <td>{$currentStep?.title || ''}</td>
23
+ <td>{$currentStep?.label || ''}</td>
24
24
  <td>{totalNextStepNodes}</td>
25
25
  <td>{$stepsToOrigin[0]} - {$stepsToOrigin[1]}</td>
26
26
  <td>{$stepsToLeaf[0]} - {$stepsToLeaf[1]}</td>
@@ -9,7 +9,7 @@
9
9
  let zoom = 1
10
10
  let selectedNode = null
11
11
  let highlightedPath: string[][] = []
12
- let error = null
12
+ let error: string | null = null
13
13
  const loading = false
14
14
 
15
15
  let treeRenderingDone = false
@@ -19,7 +19,7 @@
19
19
  $: steps = $tree.steps
20
20
  $: nodes = $tree.nodes
21
21
  $: edges = $tree.edges
22
- $: conditionals = $tree.conditionals
22
+ $: conditions = $tree.conditions
23
23
 
24
24
  let zoomBehavior: d3.ZoomBehavior<SVGSVGElement, unknown>
25
25
 
@@ -90,7 +90,7 @@
90
90
  d3.select(svgEl).selectAll('*').remove()
91
91
 
92
92
  const stepsMap = new Map(steps.map((s) => [s.id, s]))
93
- const conditionalsMap = new Map(conditionals.map((c) => [c.id, c]))
93
+ const conditionsMap = new Map(conditions.map((c) => [c.id, c]))
94
94
  const outgoingEdges = new Map<string, ExperienceEdge[]>()
95
95
  edges.forEach((e) => {
96
96
  if (!outgoingEdges.has(e.sourceNodeId)) outgoingEdges.set(e.sourceNodeId, [])
@@ -101,10 +101,10 @@
101
101
  if (node.type === 'origin') return 'Start'
102
102
  if (node.type === 'step' && node.stepId)
103
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)
104
+ if (node.type === 'condition' && node.conditionId) {
105
+ const condition = conditionsMap.get(node.conditionId)
106
+ if (condition?.label) return condition.label
107
+ if (condition?.condition) return formatLogicRule(condition.condition)
108
108
  return 'Condition'
109
109
  }
110
110
  return 'Unknown Node'
@@ -197,7 +197,7 @@
197
197
  .attr('class', 'link')
198
198
  .attr('fill', 'none')
199
199
  .attr('stroke', (d: any) => {
200
- if (d.source.data.type !== 'conditional') return '#94a3b8'
200
+ if (d.source.data.type !== 'condition') return '#94a3b8'
201
201
  const targetId = d.target.data._isReference
202
202
  ? d.target.data._referenceTo
203
203
  : d.target.data.id
@@ -211,9 +211,9 @@
211
211
  .attr('stroke-width', 2)
212
212
  .attr('d', linkGenerator)
213
213
 
214
- // Label true/false branches from conditional nodes
214
+ // Label true/false branches from condition nodes
215
215
  g.selectAll('.edge-label')
216
- .data(treeRoot.links().filter((d: any) => d.source.data.type === 'conditional'))
216
+ .data(treeRoot.links().filter((d: any) => d.source.data.type === 'condition'))
217
217
  .enter()
218
218
  .append('text')
219
219
  .attr('class', 'edge-label')
@@ -259,7 +259,7 @@
259
259
  .attr('fill', (d: any) => {
260
260
  if (d.data._isReference) return '#38bdf8'
261
261
  if (d.data.type === 'origin') return '#00cd90'
262
- if (d.data.type === 'conditional') return '#f59e0b'
262
+ if (d.data.type === 'condition') return '#f59e0b'
263
263
  return '#1748ff'
264
264
  })
265
265
  .attr('stroke', '#fff')
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import type { ResponseListItem, PrimitiveFieldValue, Field } from '@picked/client'
2
+ import type { ResponseListItem, PrimitiveFieldValue, Field, FieldValue } from '@picked/client'
3
3
  import { getPicked } from '../../../../hooks'
4
4
  import { Button, Text, CodeBlock, Banner, Stack } from '../../ui'
5
5
 
@@ -11,9 +11,10 @@
11
11
  let isEditing = false
12
12
  let editValue = ''
13
13
  let editError = ''
14
- let selectedOptions: Set<PrimitiveFieldValue> = new Set()
14
+ let selectedOptions: Set<FieldValue> = new Set()
15
15
 
16
- $: hasOptions = field?.options && field.options.length > 0
16
+ $: hasOptions = Array.isArray(field?.options) && field.options.length > 0
17
+ $: staticOptions = Array.isArray(field?.options) ? field.options : null
17
18
  $: currentValue = response?.value ?? null
18
19
 
19
20
  function formatValue(value: unknown): string {
@@ -52,7 +53,7 @@
52
53
  selectedOptions = new Set()
53
54
  }
54
55
 
55
- function toggleOption(optionValue: PrimitiveFieldValue) {
56
+ function toggleOption(optionValue: FieldValue) {
56
57
  if (field?.multiple) {
57
58
  if (selectedOptions.has(optionValue)) {
58
59
  selectedOptions.delete(optionValue)
@@ -65,7 +66,7 @@
65
66
  }
66
67
  }
67
68
 
68
- function parseValue(input: string): unknown {
69
+ function parseValue(input: string): FieldValue {
69
70
  input = input.trim()
70
71
 
71
72
  if (input === '' || input === '-') {
@@ -99,14 +100,14 @@
99
100
 
100
101
  async function saveEdit() {
101
102
  try {
102
- let newValue: unknown
103
+ let newValue: FieldValue
103
104
 
104
105
  if (hasOptions) {
105
106
  const values = Array.from(selectedOptions)
106
107
  if (field?.multiple) {
107
108
  newValue = values
108
109
  } else {
109
- newValue = values[0] ?? null
110
+ newValue = values.at(0) ?? null
110
111
  }
111
112
  } else {
112
113
  newValue = parseValue(editValue)
@@ -142,9 +143,9 @@
142
143
  <td>
143
144
  {#if isEditing}
144
145
  <Stack direction="vertical" gap="small">
145
- {#if hasOptions && field?.options}
146
+ {#if hasOptions && staticOptions}
146
147
  <Stack direction="vertical" gap="small">
147
- {#each field.options as option (option.id)}
148
+ {#each staticOptions as option (option.id)}
148
149
  <label class="option-label">
149
150
  {#if field.multiple}
150
151
  <input
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@picked/svelte",
3
- "version": "0.0.50-next.4",
3
+ "version": "0.0.51-next.1",
4
4
  "dependencies": {
5
5
  "d3": "^7.9.0"
6
6
  },
@@ -18,7 +18,7 @@
18
18
  "@vitest/coverage-v8": "^4.1.0",
19
19
  "effector": "^23.3.0",
20
20
  "fast-glob": "^3.3.3",
21
- "happy-dom": "^20.0.2",
21
+ "happy-dom": "^20.8.8",
22
22
  "size-limit": "^12.0.0",
23
23
  "svelte": "^5.46.4",
24
24
  "svelte-check": "^4.2.2",
@@ -88,7 +88,7 @@
88
88
  },
89
89
  "module": "./dist/index.js",
90
90
  "peerDependencies": {
91
- "@picked/client": "0.0.50-next.4",
91
+ "@picked/client": "0.0.51-next.1",
92
92
  "effector": "^23.3.0",
93
93
  "svelte": "^5.46.4"
94
94
  },
@@ -108,7 +108,7 @@
108
108
  "svelte-check": "svelte-check --tsconfig ./tsconfig.json",
109
109
  "svelte-check:report": "mkdir -p ./.reports && pnpm svelte-check --tsconfig ./tsconfig.json --output=machine > ./.reports/svelte-check.txt || [ -f './.reports/svelte-check.txt' ]",
110
110
  "test": "vitest run",
111
- "test:coverage": "vitest run --coverage",
111
+ "test:coverage": "../../scripts/vitest-coverage.sh",
112
112
  "test:packaged-types": "attw --pack",
113
113
  "test:watch": "vitest",
114
114
  "type-check": "tsgo -p . --noEmit"