@picked/svelte 0.0.1-dev.1767845350659 → 0.0.1-dev.1767924764002
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/form.svelte.d.ts +186 -0
- package/dist/form/form.svelte.ts +451 -0
- package/dist/form/index.d.ts +1 -16
- package/dist/form/index.js +1 -1
- package/dist/hooks/index.d.ts +5 -3
- package/dist/hooks/index.js +1 -1
- package/dist/hooks/step.d.ts +4 -5
- package/dist/hooks/step.js +1 -1
- package/dist/index.d.ts +0 -1
- package/package.json +2 -7
- package/dist/picked-elements.d.js +0 -1
- package/dist/picked-elements.d.ts +0 -10
- package/dist/picked-elements.js +0 -1
- package/dist/vite-env.d.ts +0 -2
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { FieldValue, ValidationError } from '@picked/client';
|
|
2
|
+
import { EventCallable, Store } from 'effector';
|
|
3
|
+
type ExtractFieldIds<TFieldIds extends readonly string[]> = TFieldIds[number];
|
|
4
|
+
interface GetFormOptions<TFieldIds extends readonly string[]> {
|
|
5
|
+
id: string;
|
|
6
|
+
title: string;
|
|
7
|
+
fieldIds: TFieldIds;
|
|
8
|
+
}
|
|
9
|
+
type SuccessCallback<TFieldIds extends string = string> = (options: {
|
|
10
|
+
data: Record<TFieldIds, FieldValue>;
|
|
11
|
+
stopNavigation: EventCallable<void>;
|
|
12
|
+
}) => void | Promise<void>;
|
|
13
|
+
type ErrorCallback = (errors: ValidationError[]) => void | Promise<void>;
|
|
14
|
+
type SubmitCallback<TFieldIds extends string = string> = (data: Record<TFieldIds, FieldValue>) => Record<TFieldIds, FieldValue> | void | Promise<Record<TFieldIds, FieldValue> | void>;
|
|
15
|
+
type SubmitEventWithCurrentTarget = SubmitEvent & {
|
|
16
|
+
currentTarget: EventTarget & HTMLFormElement;
|
|
17
|
+
};
|
|
18
|
+
export declare function getForm<const TFieldIds extends readonly string[]>({ id: formId, title, fieldIds, }: GetFormOptions<TFieldIds>): {
|
|
19
|
+
form: {
|
|
20
|
+
onsubmit: EventCallable<SubmitEventWithCurrentTarget>;
|
|
21
|
+
};
|
|
22
|
+
fields: Store<({
|
|
23
|
+
value: FieldValue | undefined;
|
|
24
|
+
type: "text";
|
|
25
|
+
multiple: false;
|
|
26
|
+
defaultValue?: string;
|
|
27
|
+
options?: {
|
|
28
|
+
id: string;
|
|
29
|
+
label: string;
|
|
30
|
+
description?: string;
|
|
31
|
+
value: string;
|
|
32
|
+
condition?: import('node_modules/@picked/client/dist/types/logic-engine').LogicRule;
|
|
33
|
+
}[] | null;
|
|
34
|
+
id: string;
|
|
35
|
+
name: string;
|
|
36
|
+
label: string;
|
|
37
|
+
required: boolean;
|
|
38
|
+
condition?: import('node_modules/@picked/client/dist/types/logic-engine').LogicRule;
|
|
39
|
+
placeholder?: string;
|
|
40
|
+
description?: string;
|
|
41
|
+
helpText?: string;
|
|
42
|
+
validation?: import('node_modules/@picked/client/dist/types/logic-engine').LogicRule;
|
|
43
|
+
policies?: {
|
|
44
|
+
doNotTrackValue?: boolean;
|
|
45
|
+
doNotStoreValue?: boolean;
|
|
46
|
+
};
|
|
47
|
+
} | {
|
|
48
|
+
value: FieldValue | undefined;
|
|
49
|
+
type: "text";
|
|
50
|
+
multiple: true;
|
|
51
|
+
defaultValue?: string[];
|
|
52
|
+
options?: {
|
|
53
|
+
id: string;
|
|
54
|
+
label: string;
|
|
55
|
+
description?: string;
|
|
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;
|
|
66
|
+
}[] | null;
|
|
67
|
+
id: string;
|
|
68
|
+
name: string;
|
|
69
|
+
label: string;
|
|
70
|
+
required: boolean;
|
|
71
|
+
condition?: import('node_modules/@picked/client/dist/types/logic-engine').LogicRule;
|
|
72
|
+
placeholder?: string;
|
|
73
|
+
description?: string;
|
|
74
|
+
helpText?: string;
|
|
75
|
+
validation?: import('node_modules/@picked/client/dist/types/logic-engine').LogicRule;
|
|
76
|
+
policies?: {
|
|
77
|
+
doNotTrackValue?: boolean;
|
|
78
|
+
doNotStoreValue?: boolean;
|
|
79
|
+
};
|
|
80
|
+
} | {
|
|
81
|
+
value: FieldValue | undefined;
|
|
82
|
+
type: "number";
|
|
83
|
+
multiple: false;
|
|
84
|
+
defaultValue?: number;
|
|
85
|
+
options?: {
|
|
86
|
+
id: string;
|
|
87
|
+
label: string;
|
|
88
|
+
description?: string;
|
|
89
|
+
value: number;
|
|
90
|
+
condition?: import('node_modules/@picked/client/dist/types/logic-engine').LogicRule;
|
|
91
|
+
}[] | null;
|
|
92
|
+
id: string;
|
|
93
|
+
name: string;
|
|
94
|
+
label: string;
|
|
95
|
+
required: boolean;
|
|
96
|
+
condition?: import('node_modules/@picked/client/dist/types/logic-engine').LogicRule;
|
|
97
|
+
placeholder?: string;
|
|
98
|
+
description?: string;
|
|
99
|
+
helpText?: string;
|
|
100
|
+
validation?: import('node_modules/@picked/client/dist/types/logic-engine').LogicRule;
|
|
101
|
+
policies?: {
|
|
102
|
+
doNotTrackValue?: boolean;
|
|
103
|
+
doNotStoreValue?: boolean;
|
|
104
|
+
};
|
|
105
|
+
} | {
|
|
106
|
+
value: FieldValue | undefined;
|
|
107
|
+
type: "number";
|
|
108
|
+
multiple: true;
|
|
109
|
+
defaultValue?: number[];
|
|
110
|
+
options?: {
|
|
111
|
+
id: string;
|
|
112
|
+
label: string;
|
|
113
|
+
description?: string;
|
|
114
|
+
value: number;
|
|
115
|
+
condition?: import('node_modules/@picked/client/dist/types/logic-engine').LogicRule;
|
|
116
|
+
}[] | null;
|
|
117
|
+
id: string;
|
|
118
|
+
name: string;
|
|
119
|
+
label: string;
|
|
120
|
+
required: boolean;
|
|
121
|
+
condition?: import('node_modules/@picked/client/dist/types/logic-engine').LogicRule;
|
|
122
|
+
placeholder?: string;
|
|
123
|
+
description?: string;
|
|
124
|
+
helpText?: string;
|
|
125
|
+
validation?: import('node_modules/@picked/client/dist/types/logic-engine').LogicRule;
|
|
126
|
+
policies?: {
|
|
127
|
+
doNotTrackValue?: boolean;
|
|
128
|
+
doNotStoreValue?: boolean;
|
|
129
|
+
};
|
|
130
|
+
} | {
|
|
131
|
+
value: FieldValue | undefined;
|
|
132
|
+
type: "boolean";
|
|
133
|
+
multiple: false;
|
|
134
|
+
defaultValue?: boolean;
|
|
135
|
+
options?: {
|
|
136
|
+
id: string;
|
|
137
|
+
label: string;
|
|
138
|
+
description?: string;
|
|
139
|
+
value: boolean;
|
|
140
|
+
condition?: import('node_modules/@picked/client/dist/types/logic-engine').LogicRule;
|
|
141
|
+
}[] | null;
|
|
142
|
+
id: string;
|
|
143
|
+
name: string;
|
|
144
|
+
label: string;
|
|
145
|
+
required: boolean;
|
|
146
|
+
condition?: import('node_modules/@picked/client/dist/types/logic-engine').LogicRule;
|
|
147
|
+
placeholder?: string;
|
|
148
|
+
description?: string;
|
|
149
|
+
helpText?: string;
|
|
150
|
+
validation?: import('node_modules/@picked/client/dist/types/logic-engine').LogicRule;
|
|
151
|
+
policies?: {
|
|
152
|
+
doNotTrackValue?: boolean;
|
|
153
|
+
doNotStoreValue?: boolean;
|
|
154
|
+
};
|
|
155
|
+
} | {
|
|
156
|
+
value: FieldValue | undefined;
|
|
157
|
+
type: "boolean";
|
|
158
|
+
multiple: true;
|
|
159
|
+
defaultValue?: boolean[];
|
|
160
|
+
options?: {
|
|
161
|
+
id: string;
|
|
162
|
+
label: string;
|
|
163
|
+
description?: string;
|
|
164
|
+
value: boolean;
|
|
165
|
+
condition?: import('node_modules/@picked/client/dist/types/logic-engine').LogicRule;
|
|
166
|
+
}[] | null;
|
|
167
|
+
id: string;
|
|
168
|
+
name: string;
|
|
169
|
+
label: string;
|
|
170
|
+
required: boolean;
|
|
171
|
+
condition?: import('node_modules/@picked/client/dist/types/logic-engine').LogicRule;
|
|
172
|
+
placeholder?: string;
|
|
173
|
+
description?: string;
|
|
174
|
+
helpText?: string;
|
|
175
|
+
validation?: import('node_modules/@picked/client/dist/types/logic-engine').LogicRule;
|
|
176
|
+
policies?: {
|
|
177
|
+
doNotTrackValue?: boolean;
|
|
178
|
+
doNotStoreValue?: boolean;
|
|
179
|
+
};
|
|
180
|
+
})[]>;
|
|
181
|
+
onFormSubmit: (callback: SubmitCallback<ExtractFieldIds<TFieldIds>>) => void;
|
|
182
|
+
onFormSuccess: (callback: SuccessCallback<ExtractFieldIds<TFieldIds>>) => void;
|
|
183
|
+
onFormError: (callback: ErrorCallback) => void;
|
|
184
|
+
isFormSubmitting: import('effector').StoreWritable<boolean>;
|
|
185
|
+
};
|
|
186
|
+
export {};
|
|
@@ -0,0 +1,451 @@
|
|
|
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
|
+
}
|
package/dist/form/index.d.ts
CHANGED
|
@@ -1,16 +1 @@
|
|
|
1
|
-
|
|
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 { getForm } from './form.svelte';
|
package/dist/form/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{getForm as e}from"./form.svelte";export{e as getForm};
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export { getPicked, getExperience } from './client';
|
|
2
|
-
export {
|
|
3
|
-
export {
|
|
2
|
+
export { getStep, setStepContext, type FieldWithValue } from './step';
|
|
3
|
+
export { getField } from './field';
|
|
4
|
+
export { getForm } from '../form';
|
|
4
5
|
export { getAction, getActionResult } from './action';
|
|
5
|
-
export {
|
|
6
|
+
export { getFeature, getFeatureResult } from './feature';
|
|
7
|
+
export { getPredicate, getPredicateResult } from './predicate';
|
|
6
8
|
export { useUnit, getContextScope, setContextScope } from './stores';
|
|
7
9
|
export { createSessionStorageStore, createLocalStorageStore } from './storage.js';
|
|
8
10
|
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{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};
|
package/dist/hooks/step.d.ts
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { Store } from 'effector';
|
|
2
|
-
import { ExperienceStep,
|
|
3
|
-
|
|
2
|
+
import { ExperienceStep, Response, Field } from '@picked/client';
|
|
3
|
+
import { getForm } from '../form';
|
|
4
|
+
export type FieldWithValue = Field & {
|
|
4
5
|
value: Response | undefined;
|
|
5
6
|
};
|
|
6
|
-
export interface StepContext {
|
|
7
|
+
export interface StepContext extends ReturnType<typeof getForm> {
|
|
7
8
|
step: Store<ExperienceStep>;
|
|
8
|
-
fields: Store<FieldWithValue[]>;
|
|
9
9
|
isActiveStep: Store<boolean>;
|
|
10
10
|
}
|
|
11
11
|
export declare function getStep(): StepContext;
|
|
12
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 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};
|
package/dist/index.d.ts
CHANGED
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.1767924764002",
|
|
4
4
|
"dependencies": {
|
|
5
5
|
"d3": "^7.9.0"
|
|
6
6
|
},
|
|
@@ -68,11 +68,6 @@
|
|
|
68
68
|
"import": "./dist/plugins/devtools/index.js",
|
|
69
69
|
"svelte": "./dist/plugins/devtools/index.js",
|
|
70
70
|
"default": "./dist/plugins/devtools/index.js"
|
|
71
|
-
},
|
|
72
|
-
"./elements": {
|
|
73
|
-
"types": "./dist/picked-elements.d.ts",
|
|
74
|
-
"import": "./dist/picked-elements.js",
|
|
75
|
-
"default": "./dist/picked-elements.js"
|
|
76
71
|
}
|
|
77
72
|
},
|
|
78
73
|
"files": [
|
|
@@ -80,7 +75,7 @@
|
|
|
80
75
|
],
|
|
81
76
|
"module": "./dist/index.js",
|
|
82
77
|
"peerDependencies": {
|
|
83
|
-
"@picked/client": "0.0.1-dev.
|
|
78
|
+
"@picked/client": "0.0.1-dev.1767924764002",
|
|
84
79
|
"effector": "^23.3.0",
|
|
85
80
|
"svelte": "^5.34.3"
|
|
86
81
|
},
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { PickedHTMLAttributes } from '@picked/client'
|
|
2
|
-
|
|
3
|
-
declare module 'svelte/elements' {
|
|
4
|
-
// Augment the base HTMLAttributes interface to include Picked custom attributes
|
|
5
|
-
// This makes picked-click, picked-scroll, etc. available on all HTML elements
|
|
6
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
7
|
-
interface HTMLAttributes<T> extends PickedHTMLAttributes {}
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export {}
|
package/dist/picked-elements.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
package/dist/vite-env.d.ts
DELETED