@picked/svelte 0.0.1-dev.1771973228237 → 0.0.1-dev.1772057362040
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/build-responses.d.ts +13 -0
- package/dist/form/build-responses.js +1 -0
- package/dist/form/create-field-evaluator.d.ts +17 -0
- package/dist/form/create-field-evaluator.js +1 -0
- package/dist/form/create-field-validation.d.ts +41 -0
- package/dist/form/create-field-validation.js +1 -0
- package/dist/form/create-form.js +1 -0
- package/dist/form/create-form.svelte.d.ts +41 -0
- package/dist/form/create-form.svelte.js +1 -0
- package/dist/form/create-submit-pipeline.js +1 -0
- package/dist/form/create-submit-pipeline.svelte.d.ts +53 -0
- package/dist/form/create-submit-pipeline.svelte.js +1 -0
- package/dist/form/get-form.d.ts +16 -0
- package/dist/form/get-form.js +1 -0
- package/dist/form/index.d.ts +2 -16
- package/dist/form/index.js +1 -1
- package/dist/hooks/field.d.ts +5 -0
- package/dist/hooks/step.d.ts +8 -8
- package/dist/hooks/step.js +1 -1
- package/dist/plugins/devtools/DevToolsExperience.svelte +27 -36
- package/dist/plugins/devtools/components/ExperienceInstanceListItem.svelte +1 -1
- package/dist/plugins/devtools/components/ExperienceInstancesTable.svelte +10 -14
- package/dist/plugins/devtools/experience.js +1 -1
- package/dist/plugins/devtools/screens/components/ComponentsListScreen.svelte +13 -17
- package/dist/plugins/devtools/screens/events/EventsTable.svelte +20 -24
- package/dist/plugins/devtools/screens/experience-instances/ExperienceInstancesListScreen.svelte +3 -1
- package/dist/plugins/devtools/screens/experiences/ExperienceDetailsScreen.svelte +25 -29
- package/dist/plugins/devtools/screens/experiences/ExperienceExperienceInstancesListScreen.svelte +3 -1
- package/dist/plugins/devtools/screens/experiences/ExperienceTreeScreen.svelte +2 -2
- package/dist/plugins/devtools/screens/features/FeaturesListScreen.svelte +9 -11
- package/dist/plugins/devtools/screens/frameworks/FrameworksListScreen.svelte +12 -16
- package/dist/plugins/devtools/screens/history/HistoryEntriesTable.svelte +10 -14
- package/dist/plugins/devtools/screens/predicates/PredicatesListScreen.svelte +12 -16
- package/dist/plugins/devtools/screens/project/ProjectScreen.svelte +1 -47
- package/dist/plugins/devtools/screens/responses/ResponseTableRow.svelte +10 -9
- package/dist/plugins/devtools/screens/responses/ResponsesTable.svelte +24 -26
- package/dist/plugins/devtools/ui/actions/Button.svelte +0 -4
- package/dist/plugins/devtools/ui/data/DataTable.svelte +3 -13
- package/package.json +2 -2
- package/dist/plugins/devtools/PickedIcon.svelte +0 -19
|
@@ -0,0 +1,13 @@
|
|
|
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 {};
|
|
@@ -0,0 +1 @@
|
|
|
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};
|
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{getStepFields as m}from"@picked/client";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};
|
|
@@ -0,0 +1,41 @@
|
|
|
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;
|
|
@@ -0,0 +1 @@
|
|
|
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};
|
|
@@ -0,0 +1 @@
|
|
|
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};
|
|
@@ -0,0 +1,41 @@
|
|
|
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;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './create-form.js';
|
|
@@ -0,0 +1 @@
|
|
|
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};
|
|
@@ -0,0 +1,53 @@
|
|
|
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;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './create-submit-pipeline.js';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ValidationError } from '@picked/client';
|
|
2
|
+
type SubmitEventWithCurrentTarget = SubmitEvent & {
|
|
3
|
+
currentTarget: EventTarget & HTMLFormElement;
|
|
4
|
+
};
|
|
5
|
+
interface GetFormOptions {
|
|
6
|
+
id: string;
|
|
7
|
+
title: string;
|
|
8
|
+
onSuccess?: () => void | Promise<void>;
|
|
9
|
+
onError?: (errors: ValidationError[]) => void | Promise<void>;
|
|
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{createEvent as g,createEffect as s,attach as u,sample as f,split as E}from"effector";import{getPicked as F,getExperience as b}from"../hooks/client.js";import"@picked/client";import{getStep as h}from"../hooks/step.js";import"svelte";import"svelte/store";import"@picked/analytics";import{buildResponsesFromFormData as S}from"./build-responses.js";function j({id:c,title:n,onError:d,onSuccess:l}){const r=F(),x=h(),o=b(),{currentStepStore:i}=o,p=g(),a=s(e=>r.responses.commit(e)),v=u({source:{step:i},effect:s(({step:e})=>{r.eventBus({type:"form_submit_success",form:{id:c,title:n},experience:o,step:e??void 0}),l?.()})}),_=u({source:{step:i},mapParams:(e,t)=>({step:t.step,errors:e.errors}),effect:s(({step:e,errors:t})=>{r.eventBus({type:"form_submit_error",form:{id:c,title:n},exception:{reason:t},experience:o,step:e??void 0}),t.forEach(m=>{r.eventBus({type:"field_validation_error",exception:{reason:[m]},experience:o,step:e??void 0})}),d?.(t)})});return f({clock:p,source:{step:i},fn:({step:e})=>({formId:c,title:n,step:e}),target:s(({formId:e,title:t,step:m})=>{r.eventBus({type:"form_submit",form:{id:e,title:t},experience:o,step:m??void 0})})}),f({clock:p,source:{stepFields:x.fields},fn:({stepFields:e},t)=>(t.preventDefault(),S(e,new FormData(t.currentTarget))),target:a}),E({source:a.doneData,match:{hasErrors:({errors:e})=>e.length>0},cases:{hasErrors:_,__:v}}),{form:{onsubmit:p}}}export{j as getForm};
|
package/dist/form/index.d.ts
CHANGED
|
@@ -1,16 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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 './create-form.svelte';
|
|
2
|
+
export { getForm } from './get-form';
|
package/dist/form/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{getForm as e}from"./get-form.js";export{e as getForm};
|
package/dist/hooks/field.d.ts
CHANGED
|
@@ -4,4 +4,9 @@ export 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
|
+
*/
|
|
7
12
|
export declare function getField(id: string): GetFieldResponse;
|
package/dist/hooks/step.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { Store } from 'effector';
|
|
2
|
-
import { ExperienceStep
|
|
3
|
-
|
|
4
|
-
|
|
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
|
|
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;
|
package/dist/hooks/step.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getContext as
|
|
1
|
+
import{getContext as x,onMount as h,setContext as A}from"svelte";import{createEffect as S,createEvent as E,sample as p}from"effector";import{STEP_CONTEXT_KEY as g}from"../context.js";import{createForm as O}from"../form/create-form.svelte";import{getStepRouter as k}from"../router/context.js";import{getPicked as C,getExperience as I}from"./client.js";function _(c){const s=I().treeStore.map(i=>{const d=i.steps.find(f=>f.id===c);if(!d)throw new Error(`Step with id "${c}" not found in the experience tree.`);return d}),l=s.map(i=>i.label),u=s.map(i=>i.fieldIds),n=O(c,l,u);return{stepStore:s,form:n}}function P(c){const{activeStep:a}=k(),s=a.map(e=>e?.id===c),{stepStore:l,form:u}=_(c),n=C(),i=S(e=>{n.fields.resolveAll(e)});function d(e){if(!e.dependsOn?.length)return!0;const r=n.responses.getAllStore.getState();return e.dependsOn.every(o=>{const t=r[o];return t!=null&&t!==""&&!(Array.isArray(t)&&t.length===0)})}const f=E();p({clock:f,source:{step:l,fields:n.fields.listStore},fn:({step:e,fields:r})=>e.fieldIds.filter(o=>!r.find(t=>t.id===o)?.dependsOn?.length),target:i});const m=S(({step:e,fields:r})=>{for(const o of e.fieldIds){const t=r.find(v=>v.id===o);t?.dependsOn?.length&&(d(t)?n.fields.resolveAll([o],{force:!0}):n.fields.setWaiting(o))}});return p({clock:f,source:{step:l,fields:n.fields.listStore},target:m}),h(()=>{f()}),p({clock:s,source:{step:l,fields:n.fields.listStore},filter:(e,r)=>r,fn:({step:e,fields:r})=>e.fieldIds.filter(o=>!r.find(t=>t.id===o)?.dependsOn?.length),target:i}),p({clock:s,source:{step:l,fields:n.fields.listStore},filter:(e,r)=>r,target:m}),A(g,{step:l,isActive:s,isActiveStep:s,...u})}function K(){return x(g)}export{K as getStep,P as setStepContext};
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
the change in the paired React component to maintain visual and behavioral parity.
|
|
7
7
|
-->
|
|
8
8
|
<script lang="ts">
|
|
9
|
-
import { getExperience
|
|
10
|
-
import { Button
|
|
9
|
+
import { getExperience } from '../../hooks'
|
|
10
|
+
import { Button } from './ui'
|
|
11
|
+
import PickedLogo from './PickedLogo.svelte'
|
|
11
12
|
import Tabs from './components/Tabs.svelte'
|
|
12
13
|
import ExperiencesListScreen from './screens/experiences/ExperiencesListScreen.svelte'
|
|
13
14
|
import ExperienceInstancesListScreen from './screens/experience-instances/ExperienceInstancesListScreen.svelte'
|
|
@@ -19,12 +20,8 @@
|
|
|
19
20
|
import ResponsesListScreen from './screens/responses/ResponsesListScreen.svelte'
|
|
20
21
|
import FeaturesListScreen from './screens/features/FeaturesListScreen.svelte'
|
|
21
22
|
import EventsListScreen from './screens/events/EventsListScreen.svelte'
|
|
22
|
-
import PickedIcon from './PickedIcon.svelte'
|
|
23
23
|
|
|
24
24
|
const experience = getExperience()
|
|
25
|
-
const client = getPicked()
|
|
26
|
-
|
|
27
|
-
const dashboardProjectUrl = `${client.dashboardUrl}/p/${client.projectId}`
|
|
28
25
|
|
|
29
26
|
const tabs = [
|
|
30
27
|
{
|
|
@@ -104,15 +101,10 @@
|
|
|
104
101
|
<div class="container">
|
|
105
102
|
<header class="toolbar" id="devtools-header">
|
|
106
103
|
<div class="toolbar-left">
|
|
107
|
-
<
|
|
108
|
-
<PickedIcon height={25} width={25}/>
|
|
109
|
-
<Heading size="small">Developer Tools</Heading>
|
|
110
|
-
</Stack>
|
|
104
|
+
<PickedLogo width={80} height={28} />
|
|
111
105
|
</div>
|
|
112
106
|
<div class="toolbar-center"></div>
|
|
113
107
|
<div class="toolbar-right">
|
|
114
|
-
<Button variant="ghost" size="small" href={client.documentationSiteUrl} target="_blank">Docs</Button>
|
|
115
|
-
<Button variant="ghost" size="small" href={dashboardProjectUrl} target="_blank">Dashboard</Button>
|
|
116
108
|
<Button variant="ghost" size="small" onclick={() => experience.close()}>Close</Button>
|
|
117
109
|
</div>
|
|
118
110
|
</header>
|
|
@@ -128,7 +120,7 @@
|
|
|
128
120
|
aria-label="Open Picked Devtools"
|
|
129
121
|
title="Open Picked Devtools (or press .)"
|
|
130
122
|
>
|
|
131
|
-
<
|
|
123
|
+
<PickedLogo />
|
|
132
124
|
</button>
|
|
133
125
|
{/if}
|
|
134
126
|
|
|
@@ -192,7 +184,6 @@
|
|
|
192
184
|
.toolbar-right {
|
|
193
185
|
display: flex;
|
|
194
186
|
align-items: center;
|
|
195
|
-
gap: var(--space-1);
|
|
196
187
|
}
|
|
197
188
|
|
|
198
189
|
.main {
|
|
@@ -223,36 +214,36 @@
|
|
|
223
214
|
background-color: var(--color-gray-50);
|
|
224
215
|
}
|
|
225
216
|
|
|
226
|
-
.devtools :global(th:first-child),
|
|
227
|
-
.devtools :global(td:first-child) {
|
|
228
|
-
padding-left: var(--space-4);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
.devtools :global(th:last-child),
|
|
232
|
-
.devtools :global(td:last-child) {
|
|
233
|
-
padding-right: var(--space-4);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
217
|
.floating-button {
|
|
237
218
|
position: fixed;
|
|
238
|
-
bottom:
|
|
219
|
+
bottom: 1.5rem;
|
|
239
220
|
right: 1.5rem;
|
|
240
|
-
|
|
221
|
+
width: 4.5rem;
|
|
222
|
+
height: 4.5rem;
|
|
223
|
+
border-radius: var(--radius-full);
|
|
241
224
|
background-color: var(--color-white);
|
|
242
|
-
|
|
243
|
-
|
|
225
|
+
border: 1px solid var(--color-gray-200);
|
|
226
|
+
box-shadow: 0 4px 12px rgba(21, 73, 255, 0.2);
|
|
227
|
+
display: flex;
|
|
228
|
+
align-items: center;
|
|
229
|
+
justify-content: center;
|
|
244
230
|
cursor: pointer;
|
|
245
231
|
z-index: 999999;
|
|
246
|
-
|
|
247
|
-
padding:
|
|
248
|
-
box-shadow: 2px 2px 4px #00000029;
|
|
249
|
-
|
|
250
|
-
&:hover {
|
|
251
|
-
background-color: #f5f5f5;
|
|
252
|
-
}
|
|
232
|
+
transition: all 0.2s ease;
|
|
233
|
+
padding: 0;
|
|
253
234
|
}
|
|
254
235
|
|
|
236
|
+
.floating-button:hover {
|
|
237
|
+
box-shadow: 0 6px 16px rgba(21, 73, 255, 0.3);
|
|
238
|
+
transform: scale(1.05);
|
|
239
|
+
}
|
|
255
240
|
|
|
241
|
+
.floating-button:active {
|
|
242
|
+
transform: scale(0.95);
|
|
243
|
+
}
|
|
256
244
|
|
|
257
|
-
|
|
245
|
+
.floating-button :global(svg) {
|
|
246
|
+
width: 2.25rem;
|
|
247
|
+
height: 2.25rem;
|
|
248
|
+
}
|
|
258
249
|
</style>
|
|
@@ -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?.
|
|
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>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { ExperienceInstance } from '@picked/client'
|
|
3
3
|
import ExperienceInstanceListItem from './ExperienceInstanceListItem.svelte'
|
|
4
|
-
import {
|
|
4
|
+
import { DataTable, EmptyState } from '../ui'
|
|
5
5
|
|
|
6
6
|
export let experienceInstances: ExperienceInstance[]
|
|
7
7
|
|
|
@@ -18,16 +18,12 @@
|
|
|
18
18
|
]
|
|
19
19
|
</script>
|
|
20
20
|
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
{/snippet}
|
|
31
|
-
</DataTable>
|
|
32
|
-
</Card>
|
|
33
|
-
</Stack>
|
|
21
|
+
<DataTable columns={columns} rows={experienceInstances}>
|
|
22
|
+
{#snippet row(item)}
|
|
23
|
+
{@const experienceInstance = item}
|
|
24
|
+
<ExperienceInstanceListItem {experienceInstance} />
|
|
25
|
+
{/snippet}
|
|
26
|
+
{#snippet empty()}
|
|
27
|
+
<EmptyState message="No experience instances" />
|
|
28
|
+
{/snippet}
|
|
29
|
+
</DataTable>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createExperience as e}from"@picked/client/plugins/create-experience";import{adaptSvelteComponent as o}from"../../component.js";import t from"./DevToolsExperience.svelte";import p from"./ui/tokens.css.js";const r=e({id:"devtools",name:"Picked Devtools",decide:{},targets:[{selector:"body",placement:"append"}],component:o(t,{css:[p]})});r.addStep({label:"Tools"});export{r as devtoolsExperience};
|
|
1
|
+
import{createExperience as e}from"@picked/client/plugins/create-experience";import{adaptSvelteComponent as o}from"../../component.js";import t from"./DevToolsExperience.svelte";import p from"./ui/tokens.css.js";const r=e({id:"svelte-devtools",name:"Picked Devtools",decide:{},targets:[{selector:"body",placement:"append"}],component:o(t,{css:[p]})});r.addStep({label:"Tools"});export{r as devtoolsExperience};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { getPicked } from '../../../../hooks'
|
|
3
|
-
import {
|
|
3
|
+
import { DataTable, EmptyState } from '../../ui'
|
|
4
4
|
|
|
5
5
|
const client = getPicked()
|
|
6
6
|
|
|
@@ -12,19 +12,15 @@
|
|
|
12
12
|
]
|
|
13
13
|
</script>
|
|
14
14
|
|
|
15
|
-
<
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
{/snippet}
|
|
28
|
-
</DataTable>
|
|
29
|
-
</Card>
|
|
30
|
-
</Stack>
|
|
15
|
+
<DataTable columns={columns} rows={$components}>
|
|
16
|
+
{#snippet row(item)}
|
|
17
|
+
{@const component = item}
|
|
18
|
+
<tr>
|
|
19
|
+
<td>{component.name}</td>
|
|
20
|
+
<td>{component.component.frameworkName}</td>
|
|
21
|
+
</tr>
|
|
22
|
+
{/snippet}
|
|
23
|
+
{#snippet empty()}
|
|
24
|
+
<EmptyState message="No components" />
|
|
25
|
+
{/snippet}
|
|
26
|
+
</DataTable>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { combine, createStore, createEvent, sample } from 'effector'
|
|
3
3
|
import { getPicked } from '../../../../hooks'
|
|
4
4
|
import EventListItem from './EventListItem.svelte'
|
|
5
|
-
import {
|
|
5
|
+
import { DataTable, EmptyState } from '../../ui'
|
|
6
6
|
import type { EventHistoryEntry } from '@picked/client'
|
|
7
7
|
|
|
8
8
|
const { events } = getPicked()
|
|
@@ -51,26 +51,22 @@
|
|
|
51
51
|
}
|
|
52
52
|
</script>
|
|
53
53
|
|
|
54
|
-
<
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
{/snippet}
|
|
74
|
-
</DataTable>
|
|
75
|
-
</Card>
|
|
76
|
-
</Stack>
|
|
54
|
+
<DataTable
|
|
55
|
+
columns={columns}
|
|
56
|
+
rows={$sortedEvents}
|
|
57
|
+
sortKey="time"
|
|
58
|
+
sortDirection={$sortDirectionStore}
|
|
59
|
+
onSort={handleSort}
|
|
60
|
+
>
|
|
61
|
+
{#snippet row(item)}
|
|
62
|
+
{@const event = item}
|
|
63
|
+
<EventListItem
|
|
64
|
+
{event}
|
|
65
|
+
isExpanded={expandedRowIds.has(event.id)}
|
|
66
|
+
onToggle={() => toggleRow(event.id)}
|
|
67
|
+
/>
|
|
68
|
+
{/snippet}
|
|
69
|
+
{#snippet empty()}
|
|
70
|
+
<EmptyState message="No events yet. Interact with the page to see events." />
|
|
71
|
+
{/snippet}
|
|
72
|
+
</DataTable>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { ExperienceStore } from '@picked/client'
|
|
3
3
|
import { getPicked } from '../../../../hooks'
|
|
4
|
-
import { Button,
|
|
4
|
+
import { Button, Text, CodeBlock, Stack } from '../../ui'
|
|
5
5
|
|
|
6
6
|
const client = getPicked()
|
|
7
7
|
export let experience: ExperienceStore
|
|
@@ -10,38 +10,34 @@
|
|
|
10
10
|
</script>
|
|
11
11
|
|
|
12
12
|
<Stack direction="vertical" gap="medium" padding="medium">
|
|
13
|
-
<
|
|
13
|
+
<div>
|
|
14
14
|
<Button variant="primary" size="small" onclick={() => client.launchExperienceById(experience.id)}>
|
|
15
15
|
Launch experience
|
|
16
16
|
</Button>
|
|
17
|
-
</
|
|
17
|
+
</div>
|
|
18
18
|
|
|
19
|
-
<
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
<Text>{$config.type}</Text>
|
|
24
|
-
</div>
|
|
19
|
+
<div>
|
|
20
|
+
<Text weight="semibold">Type</Text>
|
|
21
|
+
<Text>{$config.name}</Text>
|
|
22
|
+
</div>
|
|
25
23
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
24
|
+
<div>
|
|
25
|
+
<Text weight="semibold">Target selectors</Text>
|
|
26
|
+
<ul>
|
|
27
|
+
{#if $config.targetSelectors.length > 0}
|
|
28
|
+
{#each $config.targetSelectors as selector (selector)}
|
|
29
|
+
<li>
|
|
30
|
+
<CodeBlock code="{selector.selector} || {selector.placement}" />
|
|
31
|
+
</li>
|
|
32
|
+
{/each}
|
|
33
|
+
{:else}
|
|
34
|
+
<li><Text color="subdued">(none)</Text></li>
|
|
35
|
+
{/if}
|
|
36
|
+
</ul>
|
|
37
|
+
</div>
|
|
40
38
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
</Stack>
|
|
46
|
-
</Card>
|
|
39
|
+
<div>
|
|
40
|
+
<Text weight="semibold">Decision logic</Text>
|
|
41
|
+
<CodeBlock code={JSON.stringify($config.decisionLogic, null, 2)} />
|
|
42
|
+
</div>
|
|
47
43
|
</Stack>
|
package/dist/plugins/devtools/screens/experiences/ExperienceExperienceInstancesListScreen.svelte
CHANGED
|
@@ -10,4 +10,6 @@
|
|
|
10
10
|
$: experienceInstances = client.experienceInstances.getByExperienceId(experience.id)
|
|
11
11
|
</script>
|
|
12
12
|
|
|
13
|
-
<
|
|
13
|
+
<div>
|
|
14
|
+
<ExperienceInstancesTable experienceInstances={$experienceInstances} />
|
|
15
|
+
</div>
|
|
@@ -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
|
|
@@ -139,7 +139,7 @@
|
|
|
139
139
|
.attr(
|
|
140
140
|
'd',
|
|
141
141
|
d3
|
|
142
|
-
.linkHorizontal<d3.HierarchyPointLink<ProcessedNode>, ProcessedNode
|
|
142
|
+
.linkHorizontal<d3.HierarchyPointLink<ProcessedNode>, d3.HierarchyPointNode<ProcessedNode>>()
|
|
143
143
|
.x((d) => d.y)
|
|
144
144
|
.y((d) => d.x),
|
|
145
145
|
)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { createStore, createEvent } from 'effector'
|
|
3
3
|
import type { FeatureBucket } from '@picked/client'
|
|
4
4
|
import { getPicked, useUnit } from '../../../../hooks'
|
|
5
|
-
import {
|
|
5
|
+
import { DataTable, EmptyState, Button, Text, CopyButton, Card, CodeBlock, Stack } from '../../ui'
|
|
6
6
|
|
|
7
7
|
interface FeatureSelection {
|
|
8
8
|
included: boolean
|
|
@@ -87,9 +87,8 @@
|
|
|
87
87
|
generated URL. Recipients will see the overridden values on page load.
|
|
88
88
|
</Text>
|
|
89
89
|
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
{#snippet row(item)}
|
|
90
|
+
<DataTable columns={columns} rows={$featuresList}>
|
|
91
|
+
{#snippet row(item)}
|
|
93
92
|
{@const feature = item}
|
|
94
93
|
{@const selection = $selections[feature.id]}
|
|
95
94
|
{@const currentValue = getBucketValue(feature)}
|
|
@@ -106,7 +105,7 @@
|
|
|
106
105
|
</td>
|
|
107
106
|
<td>
|
|
108
107
|
<Text size="small" weight="medium">{feature.name}</Text>
|
|
109
|
-
<CodeBlock code={feature.
|
|
108
|
+
<CodeBlock code={feature.key} inline />
|
|
110
109
|
</td>
|
|
111
110
|
<td>
|
|
112
111
|
{#if currentValue !== null}
|
|
@@ -142,11 +141,10 @@
|
|
|
142
141
|
</tr>
|
|
143
142
|
{/if}
|
|
144
143
|
{/snippet}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
</Card>
|
|
144
|
+
{#snippet empty()}
|
|
145
|
+
<EmptyState message="No features registered" />
|
|
146
|
+
{/snippet}
|
|
147
|
+
</DataTable>
|
|
150
148
|
|
|
151
149
|
<Card>
|
|
152
150
|
{#snippet header()}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { getPicked } from '../../../../hooks'
|
|
3
|
-
import {
|
|
3
|
+
import { DataTable, EmptyState } from '../../ui'
|
|
4
4
|
|
|
5
5
|
const client = getPicked()
|
|
6
6
|
|
|
@@ -9,18 +9,14 @@
|
|
|
9
9
|
const columns = [{ key: 'name', header: 'Name' }]
|
|
10
10
|
</script>
|
|
11
11
|
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
{/snippet}
|
|
24
|
-
</DataTable>
|
|
25
|
-
</Card>
|
|
26
|
-
</Stack>
|
|
12
|
+
<DataTable columns={columns} rows={$frameworks}>
|
|
13
|
+
{#snippet row(item)}
|
|
14
|
+
{@const framework = item}
|
|
15
|
+
<tr>
|
|
16
|
+
<td>{framework.name}</td>
|
|
17
|
+
</tr>
|
|
18
|
+
{/snippet}
|
|
19
|
+
{#snippet empty()}
|
|
20
|
+
<EmptyState message="No frameworks" />
|
|
21
|
+
{/snippet}
|
|
22
|
+
</DataTable>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { HistoryEntry } from '@picked/client'
|
|
3
3
|
import HistoryEntryListItem from './HistoryEntryListItem.svelte'
|
|
4
|
-
import {
|
|
4
|
+
import { DataTable, EmptyState } from '../../ui'
|
|
5
5
|
|
|
6
6
|
export let historyEntries: HistoryEntry[]
|
|
7
7
|
|
|
@@ -14,16 +14,12 @@
|
|
|
14
14
|
]
|
|
15
15
|
</script>
|
|
16
16
|
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
{/snippet}
|
|
27
|
-
</DataTable>
|
|
28
|
-
</Card>
|
|
29
|
-
</Stack>
|
|
17
|
+
<DataTable columns={columns} rows={historyEntries}>
|
|
18
|
+
{#snippet row(item)}
|
|
19
|
+
{@const historyEntry = item}
|
|
20
|
+
<HistoryEntryListItem {historyEntry} />
|
|
21
|
+
{/snippet}
|
|
22
|
+
{#snippet empty()}
|
|
23
|
+
<EmptyState message="No history entries" />
|
|
24
|
+
{/snippet}
|
|
25
|
+
</DataTable>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { getPicked } from '../../../../hooks'
|
|
3
|
-
import {
|
|
3
|
+
import { DataTable, EmptyState } from '../../ui'
|
|
4
4
|
|
|
5
5
|
const client = getPicked()
|
|
6
6
|
|
|
@@ -9,18 +9,14 @@
|
|
|
9
9
|
const columns = [{ key: 'name', header: 'Name' }]
|
|
10
10
|
</script>
|
|
11
11
|
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
{/snippet}
|
|
24
|
-
</DataTable>
|
|
25
|
-
</Card>
|
|
26
|
-
</Stack>
|
|
12
|
+
<DataTable columns={columns} rows={$predicates}>
|
|
13
|
+
{#snippet row(item)}
|
|
14
|
+
{@const predicate = item}
|
|
15
|
+
<tr>
|
|
16
|
+
<td>{predicate.name}</td>
|
|
17
|
+
</tr>
|
|
18
|
+
{/snippet}
|
|
19
|
+
{#snippet empty()}
|
|
20
|
+
<EmptyState message="No predicates" />
|
|
21
|
+
{/snippet}
|
|
22
|
+
</DataTable>
|
|
@@ -1,47 +1 @@
|
|
|
1
|
-
<
|
|
2
|
-
import { getPicked } from '../../../../hooks'
|
|
3
|
-
import { Button, Card, CodeBlock, DataTable, Stack, Text } from '../../ui'
|
|
4
|
-
|
|
5
|
-
const client = getPicked()
|
|
6
|
-
|
|
7
|
-
const dashboardProjectUrl = `${client.dashboardUrl}/p/${client.projectId}`
|
|
8
|
-
|
|
9
|
-
const columns = [
|
|
10
|
-
{ key: 'label', header: 'Property' },
|
|
11
|
-
{ key: 'value', header: 'Value' },
|
|
12
|
-
]
|
|
13
|
-
|
|
14
|
-
const rows = [
|
|
15
|
-
{ label: 'Project ID', value: client.projectId },
|
|
16
|
-
{ label: 'Organization ID', value: client.organizationId },
|
|
17
|
-
{ label: 'API URL', value: client.apiUrl },
|
|
18
|
-
{ label: 'Dashboard URL', value: client.dashboardUrl },
|
|
19
|
-
]
|
|
20
|
-
</script>
|
|
21
|
-
|
|
22
|
-
<Stack direction="vertical" gap="medium" padding="medium">
|
|
23
|
-
<Text size="caption" color="subdued">
|
|
24
|
-
Project configuration for this Picked client instance.
|
|
25
|
-
</Text>
|
|
26
|
-
|
|
27
|
-
<Card sectioned>
|
|
28
|
-
<DataTable {columns} {rows}>
|
|
29
|
-
{#snippet row(item)}
|
|
30
|
-
<tr>
|
|
31
|
-
<td>
|
|
32
|
-
<Text size="small" weight="medium">{item.label}</Text>
|
|
33
|
-
</td>
|
|
34
|
-
<td>
|
|
35
|
-
<CodeBlock code={item.value} inline />
|
|
36
|
-
</td>
|
|
37
|
-
</tr>
|
|
38
|
-
{/snippet}
|
|
39
|
-
</DataTable>
|
|
40
|
-
</Card>
|
|
41
|
-
|
|
42
|
-
<Stack direction="horizontal" gap="small">
|
|
43
|
-
<Button variant="primary" size="small" href={dashboardProjectUrl} target="_blank">
|
|
44
|
-
Open in Dashboard
|
|
45
|
-
</Button>
|
|
46
|
-
</Stack>
|
|
47
|
-
</Stack>
|
|
1
|
+
<p>todo: project</p>
|
|
@@ -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<
|
|
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:
|
|
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):
|
|
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:
|
|
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
|
|
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 &&
|
|
146
|
+
{#if hasOptions && staticOptions}
|
|
146
147
|
<Stack direction="vertical" gap="small">
|
|
147
|
-
{#each
|
|
148
|
+
{#each staticOptions as option (option.id)}
|
|
148
149
|
<label class="option-label">
|
|
149
150
|
{#if field.multiple}
|
|
150
151
|
<input
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { ResponseListItem, Field } from '@picked/client'
|
|
3
3
|
import { getPicked } from '../../../../hooks'
|
|
4
4
|
import ResponseTableRow from './ResponseTableRow.svelte'
|
|
5
|
-
import {
|
|
5
|
+
import { DataTable, EmptyState, Button, RadioGroup, Stack } from '../../ui'
|
|
6
6
|
|
|
7
7
|
export let responses: ResponseListItem[] = []
|
|
8
8
|
|
|
@@ -51,29 +51,27 @@
|
|
|
51
51
|
$: filterOptions[2] = { value: 'unset', label: `Unset (${allFields.length - responses.length})` }
|
|
52
52
|
</script>
|
|
53
53
|
|
|
54
|
-
<Stack
|
|
55
|
-
<
|
|
56
|
-
<
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
{
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
</DataTable>
|
|
78
|
-
</Card>
|
|
54
|
+
<Stack direction="vertical" gap="none">
|
|
55
|
+
<Stack direction="horizontal" gap="small" align="space-between" justify="center" padding="small">
|
|
56
|
+
<RadioGroup
|
|
57
|
+
label="Show"
|
|
58
|
+
name="filter"
|
|
59
|
+
options={filterOptions}
|
|
60
|
+
value={filter}
|
|
61
|
+
onchange={(val) => { filter = val }}
|
|
62
|
+
orientation="horizontal"
|
|
63
|
+
/>
|
|
64
|
+
{#if responses.length > 0}
|
|
65
|
+
<Button variant="destructive" size="small" onclick={handleClearAll}>Clear All</Button>
|
|
66
|
+
{/if}
|
|
67
|
+
</Stack>
|
|
68
|
+
<DataTable columns={columns} rows={fieldRows}>
|
|
69
|
+
{#snippet row(item)}
|
|
70
|
+
{@const rowData = item}
|
|
71
|
+
<ResponseTableRow field={rowData.field} response={rowData.response} />
|
|
72
|
+
{/snippet}
|
|
73
|
+
{#snippet empty()}
|
|
74
|
+
<EmptyState message="No fields registered yet" />
|
|
75
|
+
{/snippet}
|
|
76
|
+
</DataTable>
|
|
79
77
|
</Stack>
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
type?: 'button' | 'submit' | 'reset'
|
|
19
19
|
fullWidth?: boolean
|
|
20
20
|
href?: string
|
|
21
|
-
target?: string
|
|
22
21
|
onclick?: (event: MouseEvent) => void
|
|
23
22
|
}
|
|
24
23
|
|
|
@@ -32,7 +31,6 @@
|
|
|
32
31
|
type = 'button',
|
|
33
32
|
fullWidth = false,
|
|
34
33
|
href,
|
|
35
|
-
target,
|
|
36
34
|
onclick,
|
|
37
35
|
}: Props = $props()
|
|
38
36
|
|
|
@@ -45,8 +43,6 @@
|
|
|
45
43
|
class:full-width={fullWidth}
|
|
46
44
|
class:is-disabled={isDisabled}
|
|
47
45
|
{href}
|
|
48
|
-
{target}
|
|
49
|
-
rel={target === '_blank' ? 'noopener noreferrer' : undefined}
|
|
50
46
|
aria-disabled={isDisabled || undefined}
|
|
51
47
|
tabindex={isDisabled ? -1 : undefined}
|
|
52
48
|
onclick={isDisabled
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<script lang="ts" generics="
|
|
1
|
+
<script lang="ts" generics="TRow">
|
|
2
2
|
import type { Snippet } from 'svelte'
|
|
3
3
|
|
|
4
4
|
interface Column {
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
interface Props {
|
|
11
11
|
columns: Column[]
|
|
12
|
-
rows:
|
|
13
|
-
row: Snippet<[
|
|
12
|
+
rows: TRow[]
|
|
13
|
+
row: Snippet<[TRow, number]>
|
|
14
14
|
empty?: Snippet
|
|
15
15
|
sortKey?: string | null
|
|
16
16
|
sortDirection?: 'asc' | 'desc'
|
|
@@ -131,14 +131,4 @@
|
|
|
131
131
|
:global(tbody tr.selected) {
|
|
132
132
|
background-color: color-mix(in srgb, var(--color-blue-500) 8%, var(--color-white));
|
|
133
133
|
}
|
|
134
|
-
|
|
135
|
-
th:first-child,
|
|
136
|
-
:global(tbody td:first-child) {
|
|
137
|
-
padding-left: var(--space-4);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
th:last-child,
|
|
141
|
-
:global(tbody td:last-child) {
|
|
142
|
-
padding-right: var(--space-4);
|
|
143
|
-
}
|
|
144
134
|
</style>
|
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.1772057362040",
|
|
4
4
|
"dependencies": {
|
|
5
5
|
"d3": "^7.9.0"
|
|
6
6
|
},
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
},
|
|
87
87
|
"module": "./dist/index.js",
|
|
88
88
|
"peerDependencies": {
|
|
89
|
-
"@picked/client": "0.0.1-dev.
|
|
89
|
+
"@picked/client": "0.0.1-dev.1772057362040",
|
|
90
90
|
"effector": "^23.3.0",
|
|
91
91
|
"svelte": "^5.34.3"
|
|
92
92
|
},
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
<!-- PickedIcon.svelte -->
|
|
2
|
-
<script lang="ts">
|
|
3
|
-
export let width = 48
|
|
4
|
-
export let height = 48
|
|
5
|
-
</script>
|
|
6
|
-
|
|
7
|
-
<svg {width} {height} viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
8
|
-
<g opacity="0.5">
|
|
9
|
-
<ellipse opacity="0.8" cx="32.5807" cy="7.38424" rx="6.29832" ry="6.29832" fill="#01AD7A"/>
|
|
10
|
-
<circle opacity="0.6" cx="31.4947" cy="8.47015" r="6.29832" fill="#01AD7A"/>
|
|
11
|
-
<ellipse opacity="0.4" cx="30.4088" cy="9.55607" rx="6.29832" ry="6.29832" fill="#01AD7A"/>
|
|
12
|
-
<ellipse opacity="0.2" cx="29.3229" cy="10.642" rx="6.29832" ry="6.29832" fill="#01AD7A"/>
|
|
13
|
-
<circle opacity="0.1" cx="28.237" cy="11.7279" r="6.29832" fill="#01AD7A"/>
|
|
14
|
-
</g>
|
|
15
|
-
<ellipse cx="33.6664" cy="6.29832" rx="6.29832" ry="6.29832" fill="#00CD90"/>
|
|
16
|
-
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.5997 16.1802C14.5788 16.1802 16.1832 14.5758 16.1832 12.5967C16.1832 10.6175 14.5788 9.01315 12.5997 9.01315C10.6206 9.01315 9.01617 10.6175 9.01617 12.5967C9.01617 14.5758 10.6206 16.1802 12.5997 16.1802ZM12.5997 18.895C16.0782 18.895 18.898 16.0751 18.898 12.5967C18.898 9.11821 16.0782 6.29836 12.5997 6.29836C9.12123 6.29836 6.30138 9.11821 6.30138 12.5967C6.30138 16.0751 9.12123 18.895 12.5997 18.895Z" fill="#1549FF"/>
|
|
17
|
-
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.5997 30.9486C14.5788 30.9486 16.1832 29.3442 16.1832 27.3651C16.1832 25.386 14.5788 23.7816 12.5997 23.7816C10.6206 23.7816 9.01617 25.386 9.01617 27.3651C9.01617 29.3442 10.6206 30.9486 12.5997 30.9486ZM12.5997 33.6634C16.0782 33.6634 18.898 30.8436 18.898 27.3651C18.898 23.8866 16.0782 21.0668 12.5997 21.0668C9.12123 21.0668 6.30138 23.8866 6.30138 27.3651C6.30138 30.8436 9.12123 33.6634 12.5997 33.6634Z" fill="#1549FF"/>
|
|
18
|
-
<path fill-rule="evenodd" clip-rule="evenodd" d="M27.3683 30.9486C29.3474 30.9486 30.9518 29.3442 30.9518 27.3651C30.9518 25.386 29.3474 23.7816 27.3683 23.7816C25.3891 23.7816 23.7847 25.386 23.7847 27.3651C23.7847 29.3442 25.3891 30.9486 27.3683 30.9486ZM27.3683 33.6634C30.8467 33.6634 33.6666 30.8436 33.6666 27.3651C33.6666 23.8866 30.8467 21.0668 27.3683 21.0668C23.8898 21.0668 21.0699 23.8866 21.0699 27.3651C21.0699 30.8436 23.8898 33.6634 27.3683 33.6634Z" fill="#1549FF"/>
|
|
19
|
-
</svg>
|