@picked/svelte 0.0.1-dev.1765928585401 → 0.0.1-dev.1765971320697
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/components/LazyImage.svelte +226 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/dist/form/index.d.ts +2 -5
- package/dist/form/index.js +1 -1
- package/dist/hooks/index.d.ts +1 -1
- package/dist/hooks/step.d.ts +5 -12
- package/dist/hooks/step.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/render/browser.js +1 -1
- package/package.json +2 -2
- package/dist/utils/ErrorBoundary.svelte +0 -23
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLImgAttributes } from 'svelte/elements'
|
|
3
|
+
import { onMount } from 'svelte'
|
|
4
|
+
|
|
5
|
+
type CommonProps = Omit<HTMLImgAttributes, 'src' | 'loading' | 'width' | 'height'> & {
|
|
6
|
+
src: string
|
|
7
|
+
loading?: 'eager' | 'lazy'
|
|
8
|
+
alt: string
|
|
9
|
+
sizes?: string
|
|
10
|
+
objectFit?: string
|
|
11
|
+
loader?: (params: { src: string; width: number }) => string | undefined
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type FillProps = {
|
|
15
|
+
fill: true
|
|
16
|
+
width?: never
|
|
17
|
+
height?: never
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type FixedSizeProps = {
|
|
21
|
+
fill?: false
|
|
22
|
+
width: number
|
|
23
|
+
height: number
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
type Props = (CommonProps & FillProps) | (CommonProps & FixedSizeProps)
|
|
27
|
+
|
|
28
|
+
const {
|
|
29
|
+
src,
|
|
30
|
+
width,
|
|
31
|
+
height,
|
|
32
|
+
loading = 'lazy',
|
|
33
|
+
alt,
|
|
34
|
+
sizes = '100vw',
|
|
35
|
+
objectFit = 'cover',
|
|
36
|
+
fill = false,
|
|
37
|
+
loader,
|
|
38
|
+
...restProps
|
|
39
|
+
}: Props = $props()
|
|
40
|
+
|
|
41
|
+
let imgElement = $state<HTMLImageElement | undefined>(undefined)
|
|
42
|
+
let placeholderElement = $state<HTMLDivElement | undefined>(undefined)
|
|
43
|
+
let isLoaded = $state(false)
|
|
44
|
+
let isInView = $state(false)
|
|
45
|
+
let observer: IntersectionObserver | undefined
|
|
46
|
+
|
|
47
|
+
const aspectRatio = $derived.by(() =>
|
|
48
|
+
!fill && typeof width === 'number' && typeof height === 'number' && height !== 0
|
|
49
|
+
? width / height
|
|
50
|
+
: undefined,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
const SUPPORTED_SIZES = [
|
|
54
|
+
16, 32, 48, 64, 96, 128, 256, 384, 640, 750, 828, 1080, 1200, 1920, 2048, 3840,
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
function findClosestSize(targetSize: number): number {
|
|
58
|
+
const roundedTargetSize = Math.round(targetSize)
|
|
59
|
+
return SUPPORTED_SIZES.reduce(
|
|
60
|
+
(prev, curr) =>
|
|
61
|
+
Math.abs(curr - roundedTargetSize) < Math.abs(prev - roundedTargetSize) ? curr : prev,
|
|
62
|
+
SUPPORTED_SIZES[0],
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function isConvincelyDomain(url: URL): boolean {
|
|
67
|
+
try {
|
|
68
|
+
return url.hostname.endsWith('.convincely.io')
|
|
69
|
+
} catch {
|
|
70
|
+
return false
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function isShopifyDomain(url: URL): boolean {
|
|
75
|
+
try {
|
|
76
|
+
return url.hostname === 'cdn.shopify.com'
|
|
77
|
+
} catch {
|
|
78
|
+
return false
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Add dpl param to Convincely domain URLs for deployment-specific routing.
|
|
84
|
+
*/
|
|
85
|
+
function addDplParamIfNeeded(imageSrc: string): string {
|
|
86
|
+
try {
|
|
87
|
+
const url = new URL(imageSrc, import.meta.url)
|
|
88
|
+
|
|
89
|
+
if (isConvincelyDomain(url)) {
|
|
90
|
+
// Check for Vercel deployment ID (works in both browser and server contexts)
|
|
91
|
+
const vercelDeploymentId = typeof import.meta.env !== 'undefined'
|
|
92
|
+
? import.meta.env.VERCEL_DEPLOYMENT_ID
|
|
93
|
+
: undefined
|
|
94
|
+
const vercelSkewProtection = typeof import.meta.env !== 'undefined'
|
|
95
|
+
? import.meta.env.VERCEL_SKEW_PROTECTION_ENABLED
|
|
96
|
+
: undefined
|
|
97
|
+
|
|
98
|
+
if (vercelDeploymentId && vercelSkewProtection === '1') {
|
|
99
|
+
url.searchParams.set('dpl', vercelDeploymentId)
|
|
100
|
+
}
|
|
101
|
+
return url.toString()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return imageSrc
|
|
105
|
+
} catch {
|
|
106
|
+
return imageSrc
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function getOptimizedImageUrl(imageSrc: string, targetWidth: number): string {
|
|
111
|
+
const width = findClosestSize(targetWidth)
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const url = new URL(imageSrc, import.meta.url)
|
|
115
|
+
|
|
116
|
+
// If it's a Shopify domain and a custom loader is provided, use the loader
|
|
117
|
+
if (isShopifyDomain(url) && loader) {
|
|
118
|
+
const customUrl = loader({ src: imageSrc, width })
|
|
119
|
+
if (customUrl) return customUrl
|
|
120
|
+
}
|
|
121
|
+
} catch {
|
|
122
|
+
// If URL parsing fails, continue with default behavior
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Default optimization: use img.convincely.io for all images
|
|
126
|
+
const imgUrl = getImgUrl(imageSrc)
|
|
127
|
+
const encodedUrl = encodeURIComponent(imgUrl)
|
|
128
|
+
return `https://img.convincely.io/_next/image?url=${encodedUrl}&w=${width}&q=75`
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function getImgUrl(imageSrc: string): string {
|
|
132
|
+
if (imageSrc.startsWith('data:')) {
|
|
133
|
+
return imageSrc
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const baseUrl = new URL(import.meta.url).origin
|
|
137
|
+
return imageSrc.startsWith('http') ? imageSrc : `${baseUrl}${imageSrc}`
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Add dpl param to src if it's a Convincely domain
|
|
141
|
+
const processedSrc = $derived(addDplParamIfNeeded(src))
|
|
142
|
+
|
|
143
|
+
function handleLoad() {
|
|
144
|
+
isLoaded = true
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
onMount(() => {
|
|
148
|
+
if (loading === 'lazy') {
|
|
149
|
+
const elementToObserve = placeholderElement || imgElement
|
|
150
|
+
if (elementToObserve) {
|
|
151
|
+
observer = new IntersectionObserver(
|
|
152
|
+
(entries) => {
|
|
153
|
+
entries.forEach((entry) => {
|
|
154
|
+
if (entry.isIntersecting) {
|
|
155
|
+
isInView = true
|
|
156
|
+
observer?.unobserve(entry.target)
|
|
157
|
+
}
|
|
158
|
+
})
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
rootMargin: '50px',
|
|
162
|
+
},
|
|
163
|
+
)
|
|
164
|
+
observer.observe(elementToObserve)
|
|
165
|
+
|
|
166
|
+
return () => {
|
|
167
|
+
observer?.disconnect()
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
} else if (loading === 'eager') {
|
|
171
|
+
isInView = true
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
const shouldLoadImage = $derived(loading === 'eager' || isInView)
|
|
176
|
+
|
|
177
|
+
const widthCandidates = $derived.by(() => {
|
|
178
|
+
if (typeof width === 'number' && Number.isFinite(width)) {
|
|
179
|
+
const filtered = SUPPORTED_SIZES.filter((w) => w <= width)
|
|
180
|
+
return filtered.length ? filtered : [width]
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return SUPPORTED_SIZES
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
const srcSet = $derived.by(() =>
|
|
187
|
+
widthCandidates.map((w) => `${getOptimizedImageUrl(processedSrc, w)} ${w}w`).join(', '),
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
const fallbackSrc = $derived.by(() => getOptimizedImageUrl(processedSrc, widthCandidates[0]))
|
|
191
|
+
|
|
192
|
+
const fadeClasses = $derived.by(() => {
|
|
193
|
+
if (loading === 'eager') return ''
|
|
194
|
+
return `transition-opacity duration-300 ${isLoaded ? 'opacity-100' : 'opacity-0'}`
|
|
195
|
+
})
|
|
196
|
+
</script>
|
|
197
|
+
|
|
198
|
+
{#if shouldLoadImage}
|
|
199
|
+
<img
|
|
200
|
+
{...restProps}
|
|
201
|
+
bind:this={imgElement}
|
|
202
|
+
src={fallbackSrc}
|
|
203
|
+
srcset={srcSet}
|
|
204
|
+
{sizes}
|
|
205
|
+
width={fill ? undefined : width}
|
|
206
|
+
height={fill ? undefined : height}
|
|
207
|
+
{alt}
|
|
208
|
+
style:aspect-ratio={aspectRatio}
|
|
209
|
+
style:object-fit={objectFit}
|
|
210
|
+
style:position={fill ? 'absolute' : undefined}
|
|
211
|
+
style:inset={fill ? 0 : undefined}
|
|
212
|
+
{loading}
|
|
213
|
+
onload={handleLoad}
|
|
214
|
+
class="{fadeClasses} {restProps.class || ''}"
|
|
215
|
+
data-testid="lazy-image-element"
|
|
216
|
+
/>
|
|
217
|
+
{:else}
|
|
218
|
+
<div
|
|
219
|
+
bind:this={placeholderElement}
|
|
220
|
+
style:aspect-ratio={aspectRatio}
|
|
221
|
+
style:position={fill ? 'absolute' : undefined}
|
|
222
|
+
style:inset={fill ? 0 : undefined}
|
|
223
|
+
aria-label={alt}
|
|
224
|
+
data-testid="lazy-image-placeholder"
|
|
225
|
+
></div>
|
|
226
|
+
{/if}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as LazyImage } from './LazyImage.svelte';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{default as f}from"./LazyImage.svelte";export{f as LazyImage};
|
package/dist/form/index.d.ts
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Store } from 'effector';
|
|
3
|
-
import { FieldWithValue } from '../hooks';
|
|
1
|
+
import { ValidationError } from '@picked/client';
|
|
4
2
|
interface GetFormOptions {
|
|
5
3
|
id: string;
|
|
6
4
|
title: string;
|
|
7
|
-
fields: Store<Field[] | FieldWithValue[]>;
|
|
8
5
|
onSuccess?: () => void | Promise<void>;
|
|
9
6
|
onError?: (errors: ValidationError[]) => void | Promise<void>;
|
|
10
7
|
}
|
|
11
8
|
type SubmitEventWithCurrentTarget = SubmitEvent & {
|
|
12
9
|
currentTarget: EventTarget & HTMLFormElement;
|
|
13
10
|
};
|
|
14
|
-
export declare function getForm({ id: formId, title,
|
|
11
|
+
export declare function getForm({ id: formId, title, onError, onSuccess }: GetFormOptions): {
|
|
15
12
|
form: {
|
|
16
13
|
onsubmit: import('effector').EventCallable<SubmitEventWithCurrentTarget>;
|
|
17
14
|
};
|
package/dist/form/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createEvent as k,createEffect as
|
|
1
|
+
import{createEvent as k,createEffect as m,attach as b,sample as g,split as D}from"effector";import{getPicked as F,getExperience as S}from"../hooks/client.js";import{getStep as B}from"../hooks/step.js";import"svelte";import"svelte/store";import"@picked/client";import"@picked/analytics";function z({id:f,title:l,onError:d,onSuccess:h}){const o=F(),x=B(),n=S(),{currentStepStore:u}=n,v=k(),y=m(r=>o.responses.commit(r)),A=b({source:{step:u},effect:m(({step:r})=>{o.eventBus({type:"form_submit_success",form:{id:f,title:l},experience:n,step:r??void 0}),h==null||h()})}),_=b({source:{step:u},mapParams:(r,a)=>({step:a.step,errors:r.errors}),effect:m(({step:r,errors:a})=>{o.eventBus({type:"form_submit_error",form:{id:f,title:l},exception:{reason:a},experience:n,step:r??void 0}),a.forEach(p=>{o.eventBus({type:"field_validation_error",exception:{reason:[p]},experience:n,step:r??void 0})}),d==null||d(a)})});return g({clock:v,source:{step:u},fn:({step:r})=>({formId:f,title:l,step:r}),target:m(({formId:r,title:a,step:p})=>{o.eventBus({type:"form_submit",form:{id:r,title:a},experience:n,step:p??void 0})})}),g({clock:v,source:{fields:o.fields.listStore,stepFields:x.fields,step:u},fn:({stepFields:r},a)=>{a.preventDefault();const p=new FormData(a.currentTarget),E=Array.from(p.entries()),c={data:{},push(t){const s=t.questionId,e=this.data[s];e?Array.isArray(e)&&Array.isArray(t.value)?Array.isArray(e)&&Array.isArray(t.value)&&(typeof e[0]=="string"?this.data[s]=[...e,...t.value].map(String):typeof e[0]=="number"?this.data[s]=[...e,...t.value].map(Number):typeof e[0]=="boolean"?this.data[s]=[...e,...t.value].map(i=>!!i):this.data[s]=t.value):this.data[s]=t.value:this.data[s]=t.value}};return r.forEach(t=>{let s=E.filter(([e])=>e===t.id);s.length===0&&(s=[[t.id,""]]),s.forEach(([,e])=>{const i=t.id;if(e===""){c.push({questionId:i,value:null});return}switch(t.type){case"text":c.push({questionId:i,value:t.multiple?[String(e)]:String(e)});break;case"number":c.push({questionId:i,value:t.multiple?[Number(e)]:Number(e)});break;case"boolean":c.push({questionId:i,value:t.multiple?[e==="true"]:e==="true"});break}})}),c.data},target:y}),D({source:y.doneData,match:{hasErrors:({errors:r})=>r.length>0},cases:{hasErrors:_,__:A}}),{form:{onsubmit:v}}}export{z as getForm};
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { getPicked, getExperience } from './client';
|
|
2
|
-
export { getStep, setStepContext
|
|
2
|
+
export { getStep, setStepContext } from './step';
|
|
3
3
|
export { useUnit, getContextScope, setContextScope } from './stores';
|
|
4
4
|
export { createSessionStorageStore, createLocalStorageStore } from './storage.js';
|
|
5
5
|
export { getTrackRecommendationPresentEvent } from './events';
|
package/dist/hooks/step.d.ts
CHANGED
|
@@ -1,20 +1,13 @@
|
|
|
1
1
|
import { Store } from 'effector';
|
|
2
|
-
import { ExperienceStep, Field, Response
|
|
3
|
-
|
|
4
|
-
export type FieldWithValue = Field & {
|
|
2
|
+
import { ExperienceStep, Field, Response } from '@picked/client';
|
|
3
|
+
type FieldWithValue = Field & {
|
|
5
4
|
value: Response | undefined;
|
|
6
5
|
};
|
|
7
|
-
interface
|
|
6
|
+
export interface StepContext {
|
|
8
7
|
step: Store<ExperienceStep>;
|
|
9
8
|
fields: Store<FieldWithValue[]>;
|
|
10
9
|
isActiveStep: Store<boolean>;
|
|
11
10
|
}
|
|
12
|
-
export
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
export declare function getStep(options?: {
|
|
16
|
-
onFormSuccess?: () => void | Promise<void>;
|
|
17
|
-
onFormError?: (errors: ValidationError[]) => void | Promise<void>;
|
|
18
|
-
}): StepContext;
|
|
19
|
-
export declare function setStepContext(stepId: string): BaseStepContext;
|
|
11
|
+
export declare function getStep(): StepContext;
|
|
12
|
+
export declare function setStepContext(stepId: string): StepContext;
|
|
20
13
|
export {};
|
package/dist/hooks/step.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getContext as v,setContext as
|
|
1
|
+
import{getContext as v,setContext as E}from"svelte";import{createStore as h,combine as a,attach as w,createEffect as C,sample as l}from"effector";import{getStepFields as A}from"@picked/client";import{STEP_CONTEXT_KEY as d}from"../context.js";import{getStepRouter as F}from"../router/context.js";import{getExperience as T,getPicked as P}from"./client.js";function N(){return v(d)}function O(o){const u=T(),i=P(),{activeStep:m}=F(),n=m.map(e=>(e==null?void 0:e.id)===o),s=u.treeStore.map(e=>{const t=e.steps.find(r=>r.id===o);if(!t)throw new Error(`Step with id "${o}" not found in the experience tree.`);return t}),c=h([]),S=a({fields:c,responses:i.responses.listStore},({fields:e,responses:t})=>e.map(r=>{var f;const g=(f=t.find(x=>x.fieldId===r.id))==null?void 0:f.value;return{...r,value:g}})),p=w({source:a({step:s,fields:i.fields.listStore}),effect:C(async({step:{fieldIds:e},fields:t})=>await A(i,t.filter(r=>e.includes(r.id))))});return l({source:{isActiveStep:n},filter:({isActiveStep:e})=>e,target:p}),l({source:p.doneData,target:c}),E(d,{step:s,fields:S,isActiveStep:n})}export{N as getStep,O as setStepContext};
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{adaptSvelteComponent as
|
|
1
|
+
import{adaptSvelteComponent as o,isValidAdaptedComponent as t}from"./component.js";import{default as d}from"./components/LazyImage.svelte";export{d as LazyImage,o as adaptSvelteComponent,t as isValidAdaptedComponent};
|
package/dist/render/browser.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{mount as
|
|
1
|
+
import{mount as a}from"svelte";import{isValidAdaptedComponent as d}from"../component.js";import{CLIENT_CONTEXT_KEY as f,EXPERIENCE_CONTEXT_KEY as C}from"../context.js";import{mergeArrays as u}from"../utils/array.js";function E(r,e){return new Map([[f,r],[C,e]])}const g=async(r,e,n,o,i)=>{var m,p;if(!d(e))throw new Error("The component is invalid.");const c=u((m=e.style)==null?void 0:m.sheets,i.stylesheets),l=u((p=e.style)==null?void 0:p.css,i.css);if(c.length&&c.forEach(s=>{const t=document.createElement("link");t.setAttribute("rel","stylesheet"),t.setAttribute("href",s),t.setAttribute("type","text/css"),t.setAttribute("picked-injected","true"),r.appendChild(t)}),l.length&&l.forEach(s=>{const t=document.createElement("style");t.textContent=s,t.setAttribute("type","text/css"),t.setAttribute("picked-injected","true"),r.appendChild(t)}),e.component instanceof Promise){const{default:s}=await e.component;a(s,{target:r,props:{client:n,experience:o},context:E(n,o)});return}a(e.component,{target:r,props:{client:n,experience:o},context:E(n,o)})},T=r=>{};export{E as createContext,g as renderSvelteComponent,T as unRenderSvelteComponent};
|
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.1765971320697",
|
|
4
4
|
"dependencies": {
|
|
5
5
|
"d3": "^7.9.0"
|
|
6
6
|
},
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
],
|
|
76
76
|
"module": "./dist/index.js",
|
|
77
77
|
"peerDependencies": {
|
|
78
|
-
"@picked/client": "0.0.1-dev.
|
|
78
|
+
"@picked/client": "0.0.1-dev.1765971320697",
|
|
79
79
|
"effector": "^23.3.0",
|
|
80
80
|
"svelte": "^5.34.3"
|
|
81
81
|
},
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { getPicked } from '../hooks'
|
|
3
|
-
import type { ComponentWithProps } from 'src/component'
|
|
4
|
-
|
|
5
|
-
interface Props {
|
|
6
|
-
component: ComponentWithProps
|
|
7
|
-
componentProps?: Record<string, unknown>
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const { component: InnerComponent, componentProps }: Props = $props()
|
|
11
|
-
|
|
12
|
-
function handleError(error: unknown) {
|
|
13
|
-
const client = getPicked()
|
|
14
|
-
|
|
15
|
-
client.eventBus({
|
|
16
|
-
type: 'exception',
|
|
17
|
-
level: 'error',
|
|
18
|
-
data: error,
|
|
19
|
-
})
|
|
20
|
-
}
|
|
21
|
-
</script>
|
|
22
|
-
|
|
23
|
-
<svelte:boundary onerror={handleError}><InnerComponent {...componentProps} /></svelte:boundary>
|