@picked/svelte 0.0.1-dev.1772073931707 → 0.0.1-dev.1772085077346

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/_virtual/preload-helper.js +1 -0
  2. package/dist/component.d.ts +3 -3
  3. package/dist/form/index.d.ts +16 -2
  4. package/dist/form/index.js +1 -1
  5. package/dist/hooks/field.d.ts +0 -5
  6. package/dist/hooks/step.d.ts +8 -8
  7. package/dist/hooks/step.js +1 -1
  8. package/dist/plugins/devtools/DevToolsExperience.svelte +173 -25
  9. package/dist/plugins/devtools/PickedIcon.svelte +19 -0
  10. package/dist/plugins/devtools/components/ExperienceInstanceListItem.svelte +1 -1
  11. package/dist/plugins/devtools/components/ExperienceInstancesTable.svelte +14 -10
  12. package/dist/plugins/devtools/plugin.d.ts +12 -4
  13. package/dist/plugins/devtools/plugin.js +1 -1
  14. package/dist/plugins/devtools/screens/components/ComponentsListScreen.svelte +17 -13
  15. package/dist/plugins/devtools/screens/events/EventsTable.svelte +24 -20
  16. package/dist/plugins/devtools/screens/experience-instances/ExperienceInstancesListScreen.svelte +1 -3
  17. package/dist/plugins/devtools/screens/experiences/ExperienceDetailsScreen.svelte +29 -25
  18. package/dist/plugins/devtools/screens/experiences/ExperienceExperienceInstancesListScreen.svelte +1 -3
  19. package/dist/plugins/devtools/screens/experiences/ExperienceTreeScreen.svelte +2 -2
  20. package/dist/plugins/devtools/screens/features/FeaturesListScreen.svelte +11 -9
  21. package/dist/plugins/devtools/screens/frameworks/FrameworksListScreen.svelte +16 -12
  22. package/dist/plugins/devtools/screens/history/HistoryEntriesTable.svelte +14 -10
  23. package/dist/plugins/devtools/screens/predicates/PredicatesListScreen.svelte +16 -12
  24. package/dist/plugins/devtools/screens/project/ProjectScreen.svelte +47 -1
  25. package/dist/plugins/devtools/screens/responses/ResponseTableRow.svelte +9 -10
  26. package/dist/plugins/devtools/screens/responses/ResponsesTable.svelte +26 -24
  27. package/dist/plugins/devtools/ui/actions/Button.svelte +4 -0
  28. package/dist/plugins/devtools/ui/data/DataTable.svelte +13 -3
  29. package/dist/render/browser.js +1 -1
  30. package/dist/render/server.js +1 -1
  31. package/package.json +2 -2
  32. package/dist/form/build-responses.d.ts +0 -13
  33. package/dist/form/build-responses.js +0 -1
  34. package/dist/form/create-field-evaluator.d.ts +0 -17
  35. package/dist/form/create-field-evaluator.js +0 -1
  36. package/dist/form/create-field-validation.d.ts +0 -41
  37. package/dist/form/create-field-validation.js +0 -1
  38. package/dist/form/create-form.js +0 -1
  39. package/dist/form/create-form.svelte.d.ts +0 -41
  40. package/dist/form/create-form.svelte.js +0 -1
  41. package/dist/form/create-submit-pipeline.js +0 -1
  42. package/dist/form/create-submit-pipeline.svelte.d.ts +0 -53
  43. package/dist/form/create-submit-pipeline.svelte.js +0 -1
  44. package/dist/form/get-form.d.ts +0 -16
  45. package/dist/form/get-form.js +0 -1
  46. package/dist/plugins/devtools/experience.d.ts +0 -1
  47. package/dist/plugins/devtools/experience.js +0 -1
  48. package/dist/plugins/devtools/ui/tokens.css.js +0 -1
@@ -9,7 +9,7 @@
9
9
  let zoom = 1
10
10
  let selectedNode = null
11
11
  let highlightedPath: string[][] = []
12
- let error: string | null = null
12
+ let error = null
13
13
  const loading = false
14
14
 
15
15
  let treeRenderingDone = false
@@ -139,7 +139,7 @@
139
139
  .attr(
140
140
  'd',
141
141
  d3
142
- .linkHorizontal<d3.HierarchyPointLink<ProcessedNode>, d3.HierarchyPointNode<ProcessedNode>>()
142
+ .linkHorizontal<d3.HierarchyPointLink<ProcessedNode>, 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 { createStore, createEvent } from 'effector'
2
+ import { createEvent, createStore } from 'effector'
3
3
  import type { FeatureBucket } from '@picked/client'
4
4
  import { getPicked, useUnit } from '../../../../hooks'
5
- import { DataTable, EmptyState, Button, Text, CopyButton, Card, CodeBlock, Stack } from '../../ui'
5
+ import { Button, Card, CodeBlock, CopyButton, DataTable, EmptyState, Stack, Text } from '../../ui'
6
6
 
7
7
  interface FeatureSelection {
8
8
  included: boolean
@@ -87,8 +87,9 @@
87
87
  generated URL. Recipients will see the overridden values on page load.
88
88
  </Text>
89
89
 
90
- <DataTable columns={columns} rows={$featuresList}>
91
- {#snippet row(item)}
90
+ <Card sectioned>
91
+ <DataTable columns={columns} rows={$featuresList}>
92
+ {#snippet row(item)}
92
93
  {@const feature = item}
93
94
  {@const selection = $selections[feature.id]}
94
95
  {@const currentValue = getBucketValue(feature)}
@@ -105,7 +106,7 @@
105
106
  </td>
106
107
  <td>
107
108
  <Text size="small" weight="medium">{feature.name}</Text>
108
- <CodeBlock code={feature.key} inline />
109
+ <CodeBlock code={feature.id + ':' + feature.key} inline />
109
110
  </td>
110
111
  <td>
111
112
  {#if currentValue !== null}
@@ -141,10 +142,11 @@
141
142
  </tr>
142
143
  {/if}
143
144
  {/snippet}
144
- {#snippet empty()}
145
- <EmptyState message="No features registered" />
146
- {/snippet}
147
- </DataTable>
145
+ {#snippet empty()}
146
+ <EmptyState message="No features registered" />
147
+ {/snippet}
148
+ </DataTable>
149
+ </Card>
148
150
 
149
151
  <Card>
150
152
  {#snippet header()}
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { getPicked } from '../../../../hooks'
3
- import { DataTable, EmptyState } from '../../ui'
3
+ import { Card, DataTable, EmptyState, Stack } from '../../ui'
4
4
 
5
5
  const client = getPicked()
6
6
 
@@ -9,14 +9,18 @@
9
9
  const columns = [{ key: 'name', header: 'Name' }]
10
10
  </script>
11
11
 
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>
12
+ <Stack padding="medium">
13
+ <Card sectioned>
14
+ <DataTable columns={columns} rows={$frameworks}>
15
+ {#snippet row(item)}
16
+ {@const framework = item}
17
+ <tr>
18
+ <td>{framework.name}</td>
19
+ </tr>
20
+ {/snippet}
21
+ {#snippet empty()}
22
+ <EmptyState message="No frameworks" />
23
+ {/snippet}
24
+ </DataTable>
25
+ </Card>
26
+ </Stack>
@@ -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 { DataTable, EmptyState } from '../../ui'
4
+ import { Card, DataTable, EmptyState, Stack } from '../../ui'
5
5
 
6
6
  export let historyEntries: HistoryEntry[]
7
7
 
@@ -14,12 +14,16 @@
14
14
  ]
15
15
  </script>
16
16
 
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>
17
+ <Stack padding="medium">
18
+ <Card sectioned>
19
+ <DataTable columns={columns} rows={historyEntries}>
20
+ {#snippet row(item)}
21
+ {@const historyEntry = item}
22
+ <HistoryEntryListItem {historyEntry} />
23
+ {/snippet}
24
+ {#snippet empty()}
25
+ <EmptyState message="No history entries" />
26
+ {/snippet}
27
+ </DataTable>
28
+ </Card>
29
+ </Stack>
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { getPicked } from '../../../../hooks'
3
- import { DataTable, EmptyState } from '../../ui'
3
+ import { Card, DataTable, EmptyState, Stack } from '../../ui'
4
4
 
5
5
  const client = getPicked()
6
6
 
@@ -9,14 +9,18 @@
9
9
  const columns = [{ key: 'name', header: 'Name' }]
10
10
  </script>
11
11
 
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>
12
+ <Stack padding="medium">
13
+ <Card sectioned>
14
+ <DataTable columns={columns} rows={$predicates}>
15
+ {#snippet row(item)}
16
+ {@const predicate = item}
17
+ <tr>
18
+ <td>{predicate.name}</td>
19
+ </tr>
20
+ {/snippet}
21
+ {#snippet empty()}
22
+ <EmptyState message="No predicates" />
23
+ {/snippet}
24
+ </DataTable>
25
+ </Card>
26
+ </Stack>
@@ -1 +1,47 @@
1
- <p>todo: project</p>
1
+ <script lang="ts">
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,5 +1,5 @@
1
1
  <script lang="ts">
2
- import type { ResponseListItem, PrimitiveFieldValue, Field, FieldValue } from '@picked/client'
2
+ import type { ResponseListItem, PrimitiveFieldValue, Field } from '@picked/client'
3
3
  import { getPicked } from '../../../../hooks'
4
4
  import { Button, Text, CodeBlock, Banner, Stack } from '../../ui'
5
5
 
@@ -11,10 +11,9 @@
11
11
  let isEditing = false
12
12
  let editValue = ''
13
13
  let editError = ''
14
- let selectedOptions: Set<FieldValue> = new Set()
14
+ let selectedOptions: Set<PrimitiveFieldValue> = new Set()
15
15
 
16
- $: hasOptions = Array.isArray(field?.options) && field.options.length > 0
17
- $: staticOptions = Array.isArray(field?.options) ? field.options : null
16
+ $: hasOptions = field?.options && field.options.length > 0
18
17
  $: currentValue = response?.value ?? null
19
18
 
20
19
  function formatValue(value: unknown): string {
@@ -53,7 +52,7 @@
53
52
  selectedOptions = new Set()
54
53
  }
55
54
 
56
- function toggleOption(optionValue: FieldValue) {
55
+ function toggleOption(optionValue: PrimitiveFieldValue) {
57
56
  if (field?.multiple) {
58
57
  if (selectedOptions.has(optionValue)) {
59
58
  selectedOptions.delete(optionValue)
@@ -66,7 +65,7 @@
66
65
  }
67
66
  }
68
67
 
69
- function parseValue(input: string): FieldValue {
68
+ function parseValue(input: string): unknown {
70
69
  input = input.trim()
71
70
 
72
71
  if (input === '' || input === '-') {
@@ -100,14 +99,14 @@
100
99
 
101
100
  async function saveEdit() {
102
101
  try {
103
- let newValue: FieldValue
102
+ let newValue: unknown
104
103
 
105
104
  if (hasOptions) {
106
105
  const values = Array.from(selectedOptions)
107
106
  if (field?.multiple) {
108
107
  newValue = values
109
108
  } else {
110
- newValue = values.at(0) ?? null
109
+ newValue = values[0] ?? null
111
110
  }
112
111
  } else {
113
112
  newValue = parseValue(editValue)
@@ -143,9 +142,9 @@
143
142
  <td>
144
143
  {#if isEditing}
145
144
  <Stack direction="vertical" gap="small">
146
- {#if hasOptions && staticOptions}
145
+ {#if hasOptions && field?.options}
147
146
  <Stack direction="vertical" gap="small">
148
- {#each staticOptions as option (option.id)}
147
+ {#each field.options as option (option.id)}
149
148
  <label class="option-label">
150
149
  {#if field.multiple}
151
150
  <input
@@ -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 { DataTable, EmptyState, Button, RadioGroup, Stack } from '../../ui'
5
+ import { Card, DataTable, EmptyState, Button, RadioGroup, Stack } from '../../ui'
6
6
 
7
7
  export let responses: ResponseListItem[] = []
8
8
 
@@ -51,27 +51,29 @@
51
51
  $: filterOptions[2] = { value: 'unset', label: `Unset (${allFields.length - responses.length})` }
52
52
  </script>
53
53
 
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>
54
+ <Stack padding="medium">
55
+ <Card sectioned>
56
+ <Stack direction="horizontal" gap="small" align="space-between" justify="center" padding="medium">
57
+ <RadioGroup
58
+ label="Show"
59
+ name="filter"
60
+ options={filterOptions}
61
+ value={filter}
62
+ onchange={(val) => { filter = val }}
63
+ orientation="horizontal"
64
+ />
65
+ {#if responses.length > 0}
66
+ <Button variant="destructive" size="small" onclick={handleClearAll}>Clear All</Button>
67
+ {/if}
68
+ </Stack>
69
+ <DataTable columns={columns} rows={fieldRows}>
70
+ {#snippet row(item)}
71
+ {@const rowData = item}
72
+ <ResponseTableRow field={rowData.field} response={rowData.response} />
73
+ {/snippet}
74
+ {#snippet empty()}
75
+ <EmptyState message="No fields registered yet" />
76
+ {/snippet}
77
+ </DataTable>
78
+ </Card>
77
79
  </Stack>
@@ -18,6 +18,7 @@
18
18
  type?: 'button' | 'submit' | 'reset'
19
19
  fullWidth?: boolean
20
20
  href?: string
21
+ target?: string
21
22
  onclick?: (event: MouseEvent) => void
22
23
  }
23
24
 
@@ -31,6 +32,7 @@
31
32
  type = 'button',
32
33
  fullWidth = false,
33
34
  href,
35
+ target,
34
36
  onclick,
35
37
  }: Props = $props()
36
38
 
@@ -43,6 +45,8 @@
43
45
  class:full-width={fullWidth}
44
46
  class:is-disabled={isDisabled}
45
47
  {href}
48
+ {target}
49
+ rel={target === '_blank' ? 'noopener noreferrer' : undefined}
46
50
  aria-disabled={isDisabled || undefined}
47
51
  tabindex={isDisabled ? -1 : undefined}
48
52
  onclick={isDisabled
@@ -1,4 +1,4 @@
1
- <script lang="ts" generics="TRow">
1
+ <script lang="ts" generics="T">
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: TRow[]
13
- row: Snippet<[TRow, number]>
12
+ rows: T[]
13
+ row: Snippet<[T, number]>
14
14
  empty?: Snippet
15
15
  sortKey?: string | null
16
16
  sortDirection?: 'asc' | 'desc'
@@ -131,4 +131,14 @@
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
+ }
134
144
  </style>
@@ -1 +1 @@
1
- import{mount as a}from"svelte";import{isValidAdaptedComponent as u}from"../component.js";import{CLIENT_CONTEXT_KEY as d,EXPERIENCE_CONTEXT_KEY as E}from"../context.js";import{mergeArrays as p}from"../utils/array.js";import f from"../utils/ErrorBoundary.svelte";function y(n,e){return new Map([[d,n],[E,e]])}const g=async(n,e,s,o,i)=>{if(!u(e))throw new Error("The component is invalid.");const c=p(e.style?.sheets,i.stylesheets),m=p(e.style?.css,i.css);c.length&&c.forEach(r=>{const t=document.createElement("link");t.setAttribute("rel","stylesheet"),t.setAttribute("href",r),t.setAttribute("type","text/css"),t.setAttribute("picked-injected","true"),n.appendChild(t)}),m.length&&m.forEach(r=>{const t=document.createElement("style");t.textContent=r,t.setAttribute("type","text/css"),t.setAttribute("picked-injected","true"),n.appendChild(t)});const l=r=>{a(f,{target:n,props:{component:r,componentProps:{client:s,experience:o}},context:y(s,o)})};if(e.component instanceof Promise){const{default:r}=await e.component;l(r);return}l(e.component)},x=n=>{};export{y as createContext,g as renderSvelteComponent,x as unRenderSvelteComponent};
1
+ import{isLazyLoader as p}from"@picked/client";import{mount as u}from"svelte";import{isValidAdaptedComponent as d}from"../component.js";import{CLIENT_CONTEXT_KEY as f,EXPERIENCE_CONTEXT_KEY as E}from"../context.js";import{mergeArrays as l}from"../utils/array.js";import y from"../utils/ErrorBoundary.svelte";function C(n,t){return new Map([[f,n],[E,t]])}const k=async(n,t,s,i,c)=>{if(!d(t))throw new Error("The component is invalid.");const m=l(t.style?.sheets,c.stylesheets),a=l(t.style?.css,c.css);m.length&&m.forEach(r=>{const e=document.createElement("link");e.setAttribute("rel","stylesheet"),e.setAttribute("href",r),e.setAttribute("type","text/css"),e.setAttribute("picked-injected","true"),n.appendChild(e)}),a.length&&a.forEach(r=>{const e=document.createElement("style");e.textContent=r,e.setAttribute("type","text/css"),e.setAttribute("picked-injected","true"),n.appendChild(e)});const o=r=>{u(y,{target:n,props:{component:r,componentProps:{client:s,experience:i}},context:C(s,i)})};if(p(t.component)){const{default:r}=await t.component.load();o(r);return}if(t.component instanceof Promise){const{default:r}=await t.component;o(r);return}o(t.component)},N=n=>{};export{C as createContext,k as renderSvelteComponent,N as unRenderSvelteComponent};
@@ -1 +1 @@
1
- import{render as a}from"svelte/server";import{isValidAdaptedComponent as E}from"../component.js";import{CLIENT_CONTEXT_KEY as p,EXPERIENCE_CONTEXT_KEY as f}from"../context.js";import{mergeArrays as n}from"../utils/array.js";function d(t,s){return new Map([[p,t],[f,s]])}const C=async(t,s,e,o)=>{if(!E(t))throw new Error("The component is invalid.");const h=n(t.style?.sheets,o.stylesheets),m=n(t.style?.css,o.css),r=t.component instanceof Promise?a((await t.component).default,{props:{client:s,experience:e},context:d(s,e)}):a(t.component,{props:{client:s,experience:e},context:d(s,e)});return{stylesheets:h,css:m,head:r.head,html:r.html,body:r.body,hashes:r.hashes}};export{d as createContext,C as staticRenderSvelteComponent};
1
+ import{isLazyLoader as m}from"@picked/client";import{render as l}from"svelte/server";import{isValidAdaptedComponent as h}from"../component.js";import{CLIENT_CONTEXT_KEY as f,EXPERIENCE_CONTEXT_KEY as p}from"../context.js";import{mergeArrays as n}from"../utils/array.js";function c(e,t){return new Map([[f,e],[p,t]])}const w=async(e,t,o,a)=>{if(!h(e))throw new Error("The component is invalid.");const d=n(e.style?.sheets,a.stylesheets),i=n(e.style?.css,a.css);let s;m(e.component)?s=(await e.component.load()).default:e.component instanceof Promise?s=(await e.component).default:s=e.component;const r=l(s,{props:{client:t,experience:o},context:c(t,o)});return{stylesheets:d,css:i,head:r.head,html:r.html,body:r.body,hashes:r.hashes}};export{c as createContext,w as staticRenderSvelteComponent};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@picked/svelte",
3
- "version": "0.0.1-dev.1772073931707",
3
+ "version": "0.0.1-dev.1772085077346",
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.1772073931707",
89
+ "@picked/client": "0.0.1-dev.1772085077346",
90
90
  "effector": "^23.3.0",
91
91
  "svelte": "^5.34.3"
92
92
  },
@@ -1,13 +0,0 @@
1
- import { FieldBase, FieldValue } from '@picked/client';
2
- type FieldLike = Pick<FieldBase, 'id' | 'type' | 'multiple'>;
3
- /**
4
- * Parses a FormData snapshot into a typed record of field responses.
5
- *
6
- * - Fields absent from FormData are mapped to `null`.
7
- * - Empty string values are mapped to `null`.
8
- * - Repeated entries for the same field (e.g. multi-select checkboxes) are
9
- * merged into a typed array — matching the field's declared element type.
10
- * - Values are coerced to the field's declared type (text → string, number → number, boolean → boolean).
11
- */
12
- export declare function buildResponsesFromFormData(fields: FieldLike[], formData: FormData): Record<string, FieldValue>;
13
- export {};
@@ -1 +0,0 @@
1
- function i(e,o,n){const r=e[o];if(r===void 0){e[o]=n;return}if(Array.isArray(r)&&Array.isArray(n)&&n.length>0){typeof r[0]=="string"?e[o]=[...r,...n].map(String):typeof r[0]=="number"?e[o]=[...r,...n].map(Number):typeof r[0]=="boolean"?e[o]=[...r,...n].map(t=>!!t):e[o]=n;return}e[o]=n}function b(e,o){const n=Array.from(o.entries()),r={};return e.forEach(t=>{let m=n.filter(([s])=>s===t.id);m.length===0&&(m=[[t.id,""]]),m.forEach(([,s])=>{if(s===""){i(r,t.id,null);return}switch(t.type){case"text":case"object":i(r,t.id,t.multiple?[String(s)]:String(s));break;case"number":i(r,t.id,t.multiple?[Number(s)]:Number(s));break;case"boolean":i(r,t.id,t.multiple?[s==="true"]:s==="true");break}})}),r}export{b as buildResponsesFromFormData};
@@ -1,17 +0,0 @@
1
- import { FieldValue, FieldWithStatus, Picked } from '@picked/client';
2
- import { Store, StoreWritable } from 'effector';
3
- /**
4
- * Creates a reactive store of evaluated fields for a given set of field IDs.
5
- *
6
- * Fields are re-evaluated whenever:
7
- * - The available field IDs change (e.g. a step is entered)
8
- * - Any response is committed (options may depend on prior answers)
9
- * - The global field registry changes (fields registered after init)
10
- * - Staged values change (field visibility conditions may reference responses.*)
11
- *
12
- * Evaluation runs `getStepFields` with effective responses (committed + staged) as context,
13
- * so visibility conditions see live in-session values rather than only committed responses.
14
- */
15
- export declare function createFieldEvaluator(client: Picked, fieldIds: Store<string[]>, stagedValuesStore: StoreWritable<Record<string, FieldValue>>): {
16
- fieldsStore: Store<FieldWithStatus[]>;
17
- };
@@ -1 +0,0 @@
1
- import{getStepFields as m}from"@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};
@@ -1,41 +0,0 @@
1
- import { FieldValue, FieldWithStatus, Picked, ValidationError } from '@picked/client';
2
- import { EventCallable, Store, StoreWritable } from 'effector';
3
- export interface FieldValidation {
4
- /** Dispatch when a field value changes — runs validation if the field has been touched. */
5
- fieldChangeEvent: EventCallable<{
6
- fieldId: string;
7
- value: FieldValue;
8
- }>;
9
- /** Dispatch when a field loses focus — always runs full validation including required check. */
10
- fieldBlurEvent: EventCallable<{
11
- fieldId: string;
12
- value: FieldValue;
13
- }>;
14
- /** Which fields have been interacted with — used by formFieldsStore to show/hide errors. */
15
- touchedStore: StoreWritable<Partial<Record<string, boolean>>>;
16
- /**
17
- * Per-field validation errors — written by client-side validation and overwritten
18
- * by server errors on commit failure. Exposed as writable so the submit pipeline
19
- * can populate server errors and reset state on new submit attempts.
20
- */
21
- fieldErrorsStore: StoreWritable<Partial<Record<string, ValidationError>>>;
22
- /** Flat list of all current validation errors — derived from fieldErrorsStore. */
23
- formErrorsStore: Store<ValidationError[]>;
24
- /**
25
- * Current in-session field values — updated on every onChange/onBlur before commit.
26
- * Used as the responses context for condition evaluation so conditions react to
27
- * live input without writing to client.responses prematurely.
28
- */
29
- stagedValuesStore: StoreWritable<Record<string, FieldValue>>;
30
- }
31
- /**
32
- * Creates the client-side validation slice for a form.
33
- *
34
- * Responsibilities:
35
- * - Tracks which fields have been touched (first blur = touched)
36
- * - Validates on blur (full: includes required check) and on change (if already touched)
37
- * - Uses the same json-logic evaluator the server uses, so client and server results match
38
- * - Maintains stagedValuesStore: current in-session values used as responses context for
39
- * condition evaluation, avoiding premature writes to client.responses
40
- */
41
- export declare function createFieldValidation(client: Picked, fieldsStore: Store<FieldWithStatus[]>, stagedValuesStore: StoreWritable<Record<string, FieldValue>>): FieldValidation;
@@ -1 +0,0 @@
1
- import{evaluateRuleLogic as M}from"@picked/client";import{createEvent as h,createStore as y,sample as p,attach as q}from"effector";function F(s){return s===null||s===""||Array.isArray(s)&&s.length===0}function R(s,k,c){const f=h(),a=h(),u=y({});p({clock:a,source:u,fn:(r,{fieldId:e})=>({...r,[e]:!0}),target:u});const g=y({}),v=g.map(r=>Object.values(r).filter(e=>e!==void 0)),m=q({source:{fields:k,staged:c,committed:s.responses.getAllStore},effect:async({fields:r,staged:e,committed:n},{fieldId:t,value:i,checkRequired:E})=>{const o=r.find(d=>d.id===t);if(!o)return{fieldId:t,error:null};if(o.validation&&Object.keys(o.validation).length>0){const d={...n,...e},l=await M(s,o.validation,{responses:d,$input:i});if(typeof l=="string")return{fieldId:t,error:{fieldId:t,userMessage:l,systemMessage:l,value:i}};if(l===!1)return{fieldId:t,error:{fieldId:t,userMessage:"This field is invalid",systemMessage:"Field validation rule returned false.",value:i}}}return E&&o.required&&F(i)?{fieldId:t,error:{fieldId:t,userMessage:"This field is required",systemMessage:"Field is required.",value:i}}:{fieldId:t,error:null}}});return p({clock:f,source:u,filter:(r,{fieldId:e})=>r[e]===!0,fn:(r,{fieldId:e,value:n})=>({fieldId:e,value:n,checkRequired:!1}),target:m}),p({clock:a,fn:({fieldId:r,value:e})=>({fieldId:r,value:e,checkRequired:!0}),target:m}),c.on(f,(r,{fieldId:e,value:n})=>({...r,[e]:n})),c.on(a,(r,{fieldId:e,value:n})=>({...r,[e]:n})),g.on(m.doneData,(r,{fieldId:e,error:n})=>{if(n===null){const t={...r};return delete t[e],t}return{...r,[e]:n}}),{fieldChangeEvent:f,fieldBlurEvent:a,touchedStore:u,fieldErrorsStore:g,formErrorsStore:v,stagedValuesStore:c}}export{R as createFieldValidation};
@@ -1 +0,0 @@
1
- import"svelte/internal/client";import{createStore as C,combine as P,attach as j,sample as u}from"effector";import{getPicked as N,getExperience as R}from"../hooks/client.js";import{createFieldEvaluator as W}from"./create-field-evaluator.js";import{createFieldValidation as q}from"./create-field-validation.js";import{createSubmitPipeline as w}from"./create-submit-pipeline.svelte";function x(m){const t=m.currentTarget;if(t instanceof HTMLInputElement)return t.type==="checkbox"?t.checked:t.value;if(t instanceof HTMLSelectElement||t instanceof HTMLTextAreaElement)return t.value}function X(m,t,k){const r=N(),g=R(),{currentStepStore:F,goToNextStep:I}=g,d=C({}),{fieldsStore:a}=W(r,k,d),{fieldChangeEvent:E,fieldBlurEvent:T,touchedStore:b,fieldErrorsStore:p,formErrorsStore:A}=q(r,a,d),y=P({fields:a,responses:r.responses.listStore,staged:d,fieldErrors:p,touched:b},({fields:s,responses:o,staged:i,fieldErrors:S,touched:f})=>s.map(e=>({...e,value:i[e.id]??o.find(n=>n.fieldId===e.id)?.value,error:S[e.id],touched:f[e.id]??!1,onChange:n=>{const c=x(n);c!==void 0&&E({fieldId:e.id,value:c})},onBlur:n=>{const c=x(n);c!==void 0&&T({fieldId:e.id,value:c})}}))),D=j({source:{staged:d,committed:r.responses.getAllStore,allFields:r.fields.listStore},effect:({staged:s,committed:o,allFields:i},S)=>{const f={...o,...s};for(const e of S){const n=i.find(v=>v.id===e);if(!n)continue;(n.dependsOn??[]).every(v=>{const l=f[v];return l!=null&&l!==""&&!(Array.isArray(l)&&l.length===0)})?r.fields.resolveAll([e],{force:!0,context:{responses:f}}):r.fields.setWaiting(e)}}});u({clock:E,source:{fields:a},fn:({fields:s},{fieldId:o})=>s.filter(i=>i.dependsOn?.includes(o)).map(i=>i.id),target:D});const{submitEvent:H,isFormSubmitting:L,onSubmit:M,onSuccess:O,onError:V,submitStarted:h,serverErrorsReceived:B}=w({client:r,experienceInstance:g,formId:m,title:t,stepStore:F,fieldsStore:a,goToNextStep:I});return u({clock:h,fn:()=>({}),target:b}),u({clock:h,fn:()=>({}),target:p}),u({clock:B,fn:s=>Object.fromEntries(s.map(o=>[o.fieldId,o])),target:p}),{form:{onsubmit:H,errors:A,submitting:L,onSubmit:M,onSuccess:O,onError:V},fields:y}}export{X as createForm};
@@ -1,41 +0,0 @@
1
- import { FieldValue, FieldWithStatus, ValidationError } from '@picked/client';
2
- import { EventCallable, Store } from 'effector';
3
- import { ErrorCallback, SubmitCallback, SubmitEventWithCurrentTarget, SuccessCallback } from './create-submit-pipeline.svelte';
4
- export type FormField = Omit<FieldWithStatus, 'error'> & {
5
- value: FieldValue | undefined;
6
- /** Validation error for this field, or undefined if valid. */
7
- error: ValidationError | undefined;
8
- touched: boolean;
9
- onChange: (e: Event) => void;
10
- onBlur: (e: Event) => void;
11
- };
12
- /**
13
- * Return type for createForm()
14
- * Provides form handlers, reactive stores, and callback registration
15
- */
16
- export interface FormInstance<TFieldIds extends string = string> {
17
- /** Form element props (bind to <form>) */
18
- form: {
19
- onsubmit: EventCallable<SubmitEventWithCurrentTarget>;
20
- /** Flat list of all current validation errors — derived from fieldErrors */
21
- errors: Store<ValidationError[]>;
22
- /** Reactive store indicating if form is currently submitting */
23
- submitting: Store<boolean>;
24
- /** Register callbacks to transform/validate data before submission */
25
- onSubmit: (callback: SubmitCallback<TFieldIds>) => void;
26
- /** Register callbacks to run on successful form submission */
27
- onSuccess: (callback: SuccessCallback<TFieldIds>) => void;
28
- /** Register callbacks to run on form validation errors */
29
- onError: (callback: ErrorCallback) => void;
30
- };
31
- /** Reactive store of fields with current values, errors, touched state, and event handlers */
32
- fields: Store<FormField[]>;
33
- }
34
- /**
35
- * Creates a form instance tied to a set of field IDs.
36
- * Handles field evaluation, form submission, data transformation,
37
- * validation, event bus tracking, and step navigation.
38
- *
39
- * Used internally by setStepContext — access via getStep() in components.
40
- */
41
- export declare function createForm(formId: string, title: Store<string>, fieldIds: Store<string[]>): FormInstance;
@@ -1 +0,0 @@
1
- export * from './create-form.js';
@@ -1 +0,0 @@
1
- import*as C from"svelte/internal/client";import{createEvent as i,createStore as B,createEffect as l,attach as O,sample as s,split as R}from"effector";import{buildResponsesFromFormData as T}from"./build-responses.js";function k(c){let o=[];return{executeFx:l(async(...a)=>await c(o,...a)),register:a=>{const f=()=>{o=o.filter(p=>p!==a)};C.user_effect(()=>(o.push(a),f))}}}function q({client:c,experienceInstance:o,formId:u,title:m,stepStore:a,fieldsStore:f,goToNextStep:p}){const n=i(),y=i(),v=i(),_=i(),x=i(),d=B(!1),D=B(!1);D.on(_,()=>!0).on(n,()=>!1);const N=n.map(()=>{}),g=k(async(e,t)=>{await Promise.all(e.map(r=>Promise.resolve(r(t))))}),b=k(async(e,t)=>{await Promise.all(e.map(r=>Promise.resolve(r(t))))}),F=k(async(e,t)=>{let r=t;for(const E of e){const w=await Promise.resolve(E(r));w!==void 0&&(r=w)}return r}),P=l(async e=>{const t=await c.responses.commit(e);return{data:e,errors:t.errors}}),h=O({source:{step:a,title:m},mapParams:(e,t)=>({step:t.step,title:t.title,data:e.data}),effect:l(async({step:e,title:t,data:r})=>(c.eventBus({type:"form_submit_success",form:{id:u,title:t},experience:o,step:e??void 0}),r))}),S=O({source:{step:a,title:m},mapParams:(e,t)=>({step:t.step,title:t.title,errors:e.errors}),effect:l(async({step:e,title:t,errors:r})=>(c.eventBus({type:"form_submit_error",form:{id:u,title:t},exception:{reason:r},experience:o,step:e??void 0}),r.forEach(E=>{c.eventBus({type:"field_validation_error",exception:{reason:[E]},experience:o,step:e??void 0})}),r))});return s({clock:n,fn:()=>!0,target:d}),s({clock:n,source:{step:a,title:m},fn:({step:e,title:t})=>({formId:u,title:t,step:e}),target:l(({formId:e,title:t,step:r})=>{c.eventBus({type:"form_submit",form:{id:e,title:t},experience:o,step:r??void 0})})}),s({clock:n,source:{stepFields:f},fn:({stepFields:e},t)=>(t.preventDefault(),T(e,new FormData(t.currentTarget))),target:F.executeFx}),s({clock:F.executeFx.doneData,target:P}),R({source:P.doneData,match:{hasErrors:({errors:e})=>e.length>0},cases:{hasErrors:S,__:h}}),s({clock:h.doneData,target:y}),s({clock:y,fn:e=>({data:e,stopNavigation:_}),target:g.executeFx}),s({clock:g.executeFx.done,target:x}),s({clock:x,source:D,filter:e=>!e,target:p}),s({clock:S.doneData,target:v}),s({clock:v,target:b.executeFx}),s({clock:[x,b.executeFx.done],fn:()=>!1,target:d}),{submitEvent:n,isFormSubmitting:d,onSubmit:F.register,onSuccess:g.register,onError:b.register,submitStarted:N,serverErrorsReceived:v}}export{q as createSubmitPipeline};