@finsweet/webflow-apps-utils 1.0.7 → 1.0.9
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/index.d.ts +0 -3
- package/dist/index.js +0 -3
- package/dist/types/customCode.d.ts +1 -1
- package/dist/ui/components/Loader.svelte +0 -2
- package/dist/ui/components/button/Button.stories.svelte +0 -8
- package/dist/ui/components/button/Button.svelte +68 -76
- package/dist/ui/components/button/index.d.ts +1 -1
- package/dist/ui/components/button/index.js +1 -0
- package/dist/ui/components/controlled-buttons/ControlledButtons.svelte +17 -7
- package/dist/ui/components/copy-text/CopyText.svelte +48 -39
- package/dist/ui/components/divider/Divider.stories.svelte +0 -4
- package/dist/ui/components/input/index.d.ts +1 -1
- package/dist/ui/components/input/index.js +1 -0
- package/dist/ui/components/layout/Layout.svelte +0 -6
- package/dist/ui/components/layout/common/EditModeMessage.svelte +1 -1
- package/dist/ui/components/layout/examples/ExampleLayout.svelte +2 -9
- package/dist/ui/components/layout/examples/Wrapper.svelte +1 -1
- package/dist/ui/components/modal/Example.svelte +0 -7
- package/dist/ui/components/modal/Modal.stories.svelte +0 -2
- package/dist/ui/components/modal/Modal.svelte +1 -1
- package/dist/ui/components/notification/Notification.stories.svelte +0 -8
- package/dist/ui/components/section/Section.stories.svelte +0 -1
- package/dist/ui/components/text/README.md +0 -2
- package/dist/ui/components/text/Text.stories.svelte +0 -6
- package/dist/ui/components/text/Text.svelte +0 -19
- package/dist/ui/components/tooltip/Tooltip.svelte +9 -4
- package/dist/ui/icons/FinsweetLogoOutlineIcon.svelte +17 -0
- package/dist/ui/icons/FinsweetLogoOutlineIcon.svelte.d.ts +26 -0
- package/dist/ui/icons/TriangleDownIconToggle.svelte +0 -1
- package/dist/ui/icons/index.d.ts +2 -1
- package/dist/ui/icons/index.js +2 -1
- package/dist/ui/index.css +1 -1
- package/dist/ui/index.d.ts +4 -0
- package/dist/ui/index.js +4 -0
- package/dist/{providers → ui/providers}/GlobalProvider.stories.js +7 -7
- package/dist/{providers → ui/providers}/GlobalProvider.svelte +1 -1
- package/dist/{providers → ui/providers}/GlobalProviderDemo.svelte +0 -2
- package/dist/{router → ui/router}/Router.stories.js +7 -7
- package/dist/{router → ui/router}/examples/RouterExample.svelte +0 -9
- package/dist/{router → ui/router}/examples/pages/AboutPage.svelte +0 -4
- package/dist/{router → ui/router}/providers/Link.svelte +0 -2
- package/dist/{router → ui/router}/providers/Route.svelte +0 -3
- package/dist/{router → ui/router}/providers/RouterProvider.svelte +1 -1
- package/dist/{router → ui/router}/router.svelte.d.ts +0 -1
- package/dist/{router → ui/router}/router.svelte.js +3 -2
- package/dist/ui/stores/form.d.ts +280 -0
- package/dist/{stores/forms.js → ui/stores/form.js} +310 -2
- package/dist/{stores → ui/stores}/forms/Form.stories.js +5 -5
- package/dist/{stores → ui/stores}/forms/FormDemo.svelte +1 -1
- package/dist/{stores → ui/stores}/index.d.ts +1 -3
- package/dist/{stores → ui/stores}/index.js +1 -3
- package/dist/ui/utils/api/checkIfAppModeIsDesign.d.ts +4 -0
- package/dist/ui/utils/api/checkIfAppModeIsDesign.js +19 -0
- package/dist/ui/utils/api/clipboard/handlePaste.d.ts +15 -0
- package/dist/ui/utils/api/clipboard/handlePaste.js +49 -0
- package/dist/ui/utils/api/clipboard/index.d.ts +1 -0
- package/dist/ui/utils/api/clipboard/index.js +1 -0
- package/dist/ui/utils/api/getAllAssets.d.ts +11 -0
- package/dist/ui/utils/api/getAllAssets.js +20 -0
- package/dist/ui/utils/api/getFinsweetComponentsEnvironment.d.ts +8 -0
- package/dist/ui/utils/api/getFinsweetComponentsEnvironment.js +66 -0
- package/dist/ui/utils/api/index.d.ts +5 -0
- package/dist/ui/utils/api/index.js +5 -0
- package/dist/ui/utils/api/insertWithXSCP.d.ts +4 -0
- package/dist/ui/utils/api/insertWithXSCP.js +12 -0
- package/dist/{utils → ui/utils}/auth/crossWindowLogin.d.ts +1 -1
- package/dist/{utils → ui/utils}/auth/crossWindowLogin.js +1 -1
- package/dist/{utils → ui/utils}/auth/index.d.ts +1 -1
- package/dist/{utils → ui/utils}/auth/index.js +4 -6
- package/dist/{utils → ui/utils}/diff-mapper/DiffMapper.stories.js +2 -2
- package/dist/{utils → ui/utils}/diff-mapper/DiffMapperDemo.svelte +1 -1
- package/dist/{utils → ui/utils}/helpers/goto.js +2 -4
- package/dist/ui/utils/helpers/index.d.ts +1 -0
- package/dist/ui/utils/helpers/index.js +1 -0
- package/dist/ui/utils/index.d.ts +3 -0
- package/dist/ui/utils/index.js +3 -0
- package/dist/utils/custom-code/api.d.ts +1 -1
- package/dist/utils/custom-code/api.js +4 -6
- package/dist/utils/custom-code/configs.d.ts +3 -2
- package/dist/utils/custom-code/configs.js +5 -8
- package/dist/utils/custom-code/index.d.ts +1 -1
- package/dist/utils/custom-code/index.js +1 -1
- package/dist/utils/helpers/index.d.ts +0 -1
- package/dist/utils/helpers/index.js +0 -1
- package/dist/utils/index.d.ts +2 -3
- package/dist/utils/index.js +1 -3
- package/dist/utils/logger/index.d.ts +1 -2
- package/dist/utils/stores/index.d.ts +2 -0
- package/dist/utils/stores/index.js +2 -0
- package/package.json +13 -4
- package/dist/stores/forms.d.ts +0 -147
- /package/dist/{providers → ui/providers}/GlobalProvider.stories.d.ts +0 -0
- /package/dist/{providers → ui/providers}/GlobalProvider.svelte.d.ts +0 -0
- /package/dist/{providers → ui/providers}/GlobalProviderDemo.svelte.d.ts +0 -0
- /package/dist/{providers → ui/providers}/configuratorUtils.d.ts +0 -0
- /package/dist/{providers → ui/providers}/configuratorUtils.js +0 -0
- /package/dist/{providers → ui/providers}/globalContext.svelte.d.ts +0 -0
- /package/dist/{providers → ui/providers}/globalContext.svelte.js +0 -0
- /package/dist/{providers → ui/providers}/index.d.ts +0 -0
- /package/dist/{providers → ui/providers}/index.js +0 -0
- /package/dist/{providers → ui/providers}/types.d.ts +0 -0
- /package/dist/{providers → ui/providers}/types.js +0 -0
- /package/dist/{router → ui/router}/Router.stories.d.ts +0 -0
- /package/dist/{router → ui/router}/examples/RouterExample.svelte.d.ts +0 -0
- /package/dist/{router → ui/router}/examples/index.d.ts +0 -0
- /package/dist/{router → ui/router}/examples/index.js +0 -0
- /package/dist/{router → ui/router}/examples/pages/AboutPage.svelte.d.ts +0 -0
- /package/dist/{router → ui/router}/examples/pages/HomePage.svelte +0 -0
- /package/dist/{router → ui/router}/examples/pages/HomePage.svelte.d.ts +0 -0
- /package/dist/{router → ui/router}/examples/pages/NotFoundPage.svelte +0 -0
- /package/dist/{router → ui/router}/examples/pages/NotFoundPage.svelte.d.ts +0 -0
- /package/dist/{router → ui/router}/hooks.svelte.d.ts +0 -0
- /package/dist/{router → ui/router}/hooks.svelte.js +0 -0
- /package/dist/{router → ui/router}/index.d.ts +0 -0
- /package/dist/{router → ui/router}/index.js +0 -0
- /package/dist/{router → ui/router}/providers/Link.svelte.d.ts +0 -0
- /package/dist/{router → ui/router}/providers/Route.svelte.d.ts +0 -0
- /package/dist/{router → ui/router}/providers/RouterProvider.svelte.d.ts +0 -0
- /package/dist/{router → ui/router}/providers/index.d.ts +0 -0
- /package/dist/{router → ui/router}/providers/index.js +0 -0
- /package/dist/{stores → ui/stores}/breakpoints.d.ts +0 -0
- /package/dist/{stores → ui/stores}/breakpoints.js +0 -0
- /package/dist/{stores → ui/stores}/componentInjectErrors.d.ts +0 -0
- /package/dist/{stores → ui/stores}/componentInjectErrors.js +0 -0
- /package/dist/{stores → ui/stores}/forms/Form.stories.d.ts +0 -0
- /package/dist/{stores → ui/stores}/forms/FormDemo.svelte.d.ts +0 -0
- /package/dist/{stores → ui/stores}/showConfirmActionModal.d.ts +0 -0
- /package/dist/{stores → ui/stores}/showConfirmActionModal.js +0 -0
- /package/dist/{stores → ui/stores}/siteInfo.d.ts +0 -0
- /package/dist/{stores → ui/stores}/siteInfo.js +0 -0
- /package/dist/{utils → ui/utils}/diff-mapper/DiffMapper.stories.d.ts +0 -0
- /package/dist/{utils → ui/utils}/diff-mapper/DiffMapperDemo.svelte.d.ts +0 -0
- /package/dist/{utils → ui/utils}/diff-mapper/deepDiffMapper.d.ts +0 -0
- /package/dist/{utils → ui/utils}/diff-mapper/deepDiffMapper.js +0 -0
- /package/dist/{utils → ui/utils}/diff-mapper/index.d.ts +0 -0
- /package/dist/{utils → ui/utils}/diff-mapper/index.js +0 -0
- /package/dist/{utils → ui/utils}/helpers/goto.d.ts +0 -0
- /package/dist/{stores → utils/stores}/isPreviewMode.d.ts +0 -0
- /package/dist/{stores → utils/stores}/isPreviewMode.js +0 -0
- /package/dist/{stores → utils/stores}/router.d.ts +0 -0
- /package/dist/{stores → utils/stores}/router.js +0 -0
|
@@ -3,8 +3,280 @@ import { z } from 'zod';
|
|
|
3
3
|
// Registry to track all form states by identifier
|
|
4
4
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5
5
|
const formsRegistry = writable({});
|
|
6
|
+
// Registry to track field registrations per form
|
|
7
|
+
const fieldRegistrations = writable({});
|
|
6
8
|
// Validates class name according to HTML class name rules
|
|
7
9
|
const classNameRegex = /^[a-zA-Z0-9_-]+$/;
|
|
10
|
+
/**
|
|
11
|
+
* Generic Form Manager for any Zod schema with cross-component field support
|
|
12
|
+
*/
|
|
13
|
+
export class FormManager {
|
|
14
|
+
schema;
|
|
15
|
+
_store;
|
|
16
|
+
initialValues;
|
|
17
|
+
identifier;
|
|
18
|
+
fieldRegistrations = new Map();
|
|
19
|
+
// Public reactive stores that components can directly use
|
|
20
|
+
values;
|
|
21
|
+
errors;
|
|
22
|
+
touched;
|
|
23
|
+
isValid;
|
|
24
|
+
isDirty;
|
|
25
|
+
isSubmitting;
|
|
26
|
+
constructor(identifier, schema, initialValues) {
|
|
27
|
+
this.identifier = identifier;
|
|
28
|
+
this.schema = schema;
|
|
29
|
+
this.initialValues = initialValues;
|
|
30
|
+
// Create the internal form state store
|
|
31
|
+
this._store = writable({
|
|
32
|
+
values: initialValues,
|
|
33
|
+
errors: {},
|
|
34
|
+
touched: {},
|
|
35
|
+
isValid: false,
|
|
36
|
+
isDirty: false,
|
|
37
|
+
isSubmitting: false
|
|
38
|
+
});
|
|
39
|
+
// Create derived stores
|
|
40
|
+
this.values = derived(this._store, ($store) => $store.values);
|
|
41
|
+
this.errors = derived(this._store, ($store) => $store.errors);
|
|
42
|
+
this.touched = derived(this._store, ($store) => $store.touched);
|
|
43
|
+
this.isValid = derived(this._store, ($store) => $store.isValid);
|
|
44
|
+
this.isDirty = derived(this._store, ($store) => $store.isDirty);
|
|
45
|
+
this.isSubmitting = derived(this._store, ($store) => $store.isSubmitting);
|
|
46
|
+
// Register this form with the global registry
|
|
47
|
+
formsRegistry.update((registry) => {
|
|
48
|
+
registry[identifier] = this;
|
|
49
|
+
return registry;
|
|
50
|
+
});
|
|
51
|
+
// Initialize field registrations for this form
|
|
52
|
+
fieldRegistrations.update((registrations) => {
|
|
53
|
+
registrations[identifier] = {};
|
|
54
|
+
return registrations;
|
|
55
|
+
});
|
|
56
|
+
// Initial validation
|
|
57
|
+
this.validate();
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Register a field with the form for cross-component management
|
|
61
|
+
*/
|
|
62
|
+
registerField(fieldName, options) {
|
|
63
|
+
const registration = {
|
|
64
|
+
name: fieldName,
|
|
65
|
+
validate: options?.validate,
|
|
66
|
+
transform: options?.transform
|
|
67
|
+
};
|
|
68
|
+
this.fieldRegistrations.set(fieldName, registration);
|
|
69
|
+
// Update global field registrations
|
|
70
|
+
fieldRegistrations.update((registrations) => {
|
|
71
|
+
registrations[this.identifier][fieldName] =
|
|
72
|
+
registration;
|
|
73
|
+
return registrations;
|
|
74
|
+
});
|
|
75
|
+
return {
|
|
76
|
+
// Return field-specific reactive stores
|
|
77
|
+
value: derived(this.values, ($values) => $values[fieldName]),
|
|
78
|
+
error: derived(this.errors, ($errors) => $errors[fieldName] || []),
|
|
79
|
+
touched: derived(this.touched, ($touched) => $touched[fieldName] || false),
|
|
80
|
+
// Field-specific methods
|
|
81
|
+
setValue: (value) => this.setField(fieldName, value),
|
|
82
|
+
setTouched: () => this.setTouched(fieldName),
|
|
83
|
+
validate: () => this.validateField(fieldName)
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Set the value of a specific field
|
|
88
|
+
*/
|
|
89
|
+
setField(field, value) {
|
|
90
|
+
this._store.update((state) => {
|
|
91
|
+
const newState = {
|
|
92
|
+
...state,
|
|
93
|
+
values: {
|
|
94
|
+
...state.values,
|
|
95
|
+
[field]: value
|
|
96
|
+
},
|
|
97
|
+
touched: {
|
|
98
|
+
...state.touched,
|
|
99
|
+
[field]: true
|
|
100
|
+
},
|
|
101
|
+
isDirty: true
|
|
102
|
+
};
|
|
103
|
+
return newState;
|
|
104
|
+
});
|
|
105
|
+
this.validate();
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Set multiple field values at once
|
|
109
|
+
*/
|
|
110
|
+
setFields(values) {
|
|
111
|
+
this._store.update((state) => {
|
|
112
|
+
const newTouched = { ...state.touched };
|
|
113
|
+
// Mark all updated fields as touched
|
|
114
|
+
Object.keys(values).forEach((key) => {
|
|
115
|
+
newTouched[key] = true;
|
|
116
|
+
});
|
|
117
|
+
return {
|
|
118
|
+
...state,
|
|
119
|
+
values: {
|
|
120
|
+
...state.values,
|
|
121
|
+
...values
|
|
122
|
+
},
|
|
123
|
+
touched: newTouched,
|
|
124
|
+
isDirty: true
|
|
125
|
+
};
|
|
126
|
+
});
|
|
127
|
+
this.validate();
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Set a field as touched without changing its value
|
|
131
|
+
*/
|
|
132
|
+
setTouched(field) {
|
|
133
|
+
this._store.update((state) => ({
|
|
134
|
+
...state,
|
|
135
|
+
touched: {
|
|
136
|
+
...state.touched,
|
|
137
|
+
[field]: true
|
|
138
|
+
}
|
|
139
|
+
}));
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Validate a specific field
|
|
143
|
+
*/
|
|
144
|
+
validateField(field) {
|
|
145
|
+
const currentState = get(this._store);
|
|
146
|
+
const fieldValue = currentState.values[field];
|
|
147
|
+
const errors = [];
|
|
148
|
+
// Check custom field validation
|
|
149
|
+
const registration = this.fieldRegistrations.get(field);
|
|
150
|
+
if (registration?.validate) {
|
|
151
|
+
const customError = registration.validate(fieldValue);
|
|
152
|
+
if (customError) {
|
|
153
|
+
errors.push(customError);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// Run Zod validation on the entire form to get field-specific errors
|
|
157
|
+
const result = this.schema.safeParse(currentState.values);
|
|
158
|
+
if (!result.success) {
|
|
159
|
+
const zodErrors = result.error.format();
|
|
160
|
+
const fieldErrors = zodErrors[field]?._errors || [];
|
|
161
|
+
errors.push(...fieldErrors);
|
|
162
|
+
}
|
|
163
|
+
return errors;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Reset the form to initial values
|
|
167
|
+
*/
|
|
168
|
+
reset() {
|
|
169
|
+
this._store.set({
|
|
170
|
+
values: { ...this.initialValues },
|
|
171
|
+
errors: {},
|
|
172
|
+
touched: {},
|
|
173
|
+
isValid: false,
|
|
174
|
+
isDirty: false,
|
|
175
|
+
isSubmitting: false
|
|
176
|
+
});
|
|
177
|
+
this.validate();
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Set the form as submitting
|
|
181
|
+
*/
|
|
182
|
+
setSubmitting(isSubmitting) {
|
|
183
|
+
this._store.update((state) => ({
|
|
184
|
+
...state,
|
|
185
|
+
isSubmitting
|
|
186
|
+
}));
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Update the schema (useful for dynamic forms)
|
|
190
|
+
*/
|
|
191
|
+
updateSchema(newSchema) {
|
|
192
|
+
this.schema = newSchema;
|
|
193
|
+
this.validate();
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Validate the entire form
|
|
197
|
+
*/
|
|
198
|
+
validate() {
|
|
199
|
+
this._store.update((state) => {
|
|
200
|
+
const result = this.schema.safeParse(state.values);
|
|
201
|
+
const errors = {};
|
|
202
|
+
let isValid = true;
|
|
203
|
+
if (!result.success) {
|
|
204
|
+
isValid = false;
|
|
205
|
+
// Convert Zod errors to our error format
|
|
206
|
+
Object.keys(state.values).forEach((key) => {
|
|
207
|
+
const fieldKey = key;
|
|
208
|
+
const fieldErrors = result.error.format()[fieldKey]
|
|
209
|
+
?._errors || [];
|
|
210
|
+
// Add custom field validation errors
|
|
211
|
+
const registration = this.fieldRegistrations.get(key);
|
|
212
|
+
if (registration?.validate) {
|
|
213
|
+
const customError = registration.validate(state.values[fieldKey]);
|
|
214
|
+
if (customError) {
|
|
215
|
+
fieldErrors.push(customError);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (fieldErrors.length > 0) {
|
|
219
|
+
errors[fieldKey] = fieldErrors;
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
...state,
|
|
225
|
+
errors,
|
|
226
|
+
isValid
|
|
227
|
+
};
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Get the current state of the form
|
|
232
|
+
*/
|
|
233
|
+
getState() {
|
|
234
|
+
return get(this._store);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Destroy the form and clean up resources
|
|
238
|
+
*/
|
|
239
|
+
destroy() {
|
|
240
|
+
// Remove from registries
|
|
241
|
+
formsRegistry.update((registry) => {
|
|
242
|
+
delete registry[this.identifier];
|
|
243
|
+
return registry;
|
|
244
|
+
});
|
|
245
|
+
fieldRegistrations.update((registrations) => {
|
|
246
|
+
delete registrations[this.identifier];
|
|
247
|
+
return registrations;
|
|
248
|
+
});
|
|
249
|
+
// Clear field registrations
|
|
250
|
+
this.fieldRegistrations.clear();
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Creates a generic form with Zod validation
|
|
255
|
+
*/
|
|
256
|
+
export function createGenericForm(identifier, schema, initialValues) {
|
|
257
|
+
const form = new FormManager(identifier, schema, initialValues);
|
|
258
|
+
return {
|
|
259
|
+
// Reactive stores that components can directly subscribe to
|
|
260
|
+
values: form.values,
|
|
261
|
+
errors: form.errors,
|
|
262
|
+
touched: form.touched,
|
|
263
|
+
isValid: form.isValid,
|
|
264
|
+
isDirty: form.isDirty,
|
|
265
|
+
isSubmitting: form.isSubmitting,
|
|
266
|
+
// Helper methods
|
|
267
|
+
setField: form.setField.bind(form),
|
|
268
|
+
setFields: form.setFields.bind(form),
|
|
269
|
+
setTouched: form.setTouched.bind(form),
|
|
270
|
+
reset: form.reset.bind(form),
|
|
271
|
+
setSubmitting: form.setSubmitting.bind(form),
|
|
272
|
+
updateSchema: form.updateSchema.bind(form),
|
|
273
|
+
registerField: form.registerField.bind(form),
|
|
274
|
+
validateField: form.validateField.bind(form),
|
|
275
|
+
// For advanced use cases
|
|
276
|
+
getState: form.getState.bind(form),
|
|
277
|
+
destroy: form.destroy.bind(form)
|
|
278
|
+
};
|
|
279
|
+
}
|
|
8
280
|
/**
|
|
9
281
|
* Creates a form validation utility with Svelte 5 reactive stores
|
|
10
282
|
* @param identifier - Unique identifier for the form
|
|
@@ -265,7 +537,7 @@ export class FormValidator {
|
|
|
265
537
|
}));
|
|
266
538
|
}
|
|
267
539
|
/**
|
|
268
|
-
* Get the current state of the form
|
|
540
|
+
* Get the current state of the form
|
|
269
541
|
*/
|
|
270
542
|
getState() {
|
|
271
543
|
return get(this._store);
|
|
@@ -302,7 +574,6 @@ export function createForm(identifier, initialValues, options) {
|
|
|
302
574
|
/**
|
|
303
575
|
* Get a form by its identifier
|
|
304
576
|
*/
|
|
305
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
306
577
|
export function getFormById(identifier) {
|
|
307
578
|
const registry = get(formsRegistry);
|
|
308
579
|
return registry[identifier];
|
|
@@ -354,6 +625,43 @@ export function createFormSubscription(identifier) {
|
|
|
354
625
|
destroy: () => clearInterval(intervalId)
|
|
355
626
|
};
|
|
356
627
|
}
|
|
628
|
+
/**
|
|
629
|
+
* Helper to get field state for a specific form and field
|
|
630
|
+
*/
|
|
631
|
+
export function getFieldState(formId, fieldName) {
|
|
632
|
+
const form = getFormById(formId);
|
|
633
|
+
if (!form) {
|
|
634
|
+
return {
|
|
635
|
+
value: undefined,
|
|
636
|
+
error: [],
|
|
637
|
+
touched: false,
|
|
638
|
+
isValid: true
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
const state = form.getState();
|
|
642
|
+
return {
|
|
643
|
+
value: state.values[fieldName],
|
|
644
|
+
error: state.errors[fieldName] || [],
|
|
645
|
+
touched: state.touched[fieldName] || false,
|
|
646
|
+
isValid: !state.errors[fieldName] || state.errors[fieldName].length === 0
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Helper to update a field value for a specific form
|
|
651
|
+
*/
|
|
652
|
+
export function updateFieldValue(formId, fieldName, value) {
|
|
653
|
+
const form = getFormById(formId);
|
|
654
|
+
if (form && 'setField' in form) {
|
|
655
|
+
form.setField(fieldName, value);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Helper to get all registered field names for a form
|
|
660
|
+
*/
|
|
661
|
+
export function getFormFieldNames(formId) {
|
|
662
|
+
const registrations = get(fieldRegistrations);
|
|
663
|
+
return Object.keys(registrations[formId] || {});
|
|
664
|
+
}
|
|
357
665
|
/**
|
|
358
666
|
* Store to track if the form is adding or updating to the Canvas
|
|
359
667
|
*/
|
|
@@ -35,7 +35,7 @@ The form validation system provides:
|
|
|
35
35
|
The main class for creating and managing form validation state.
|
|
36
36
|
|
|
37
37
|
\`\`\`typescript
|
|
38
|
-
import { FormValidator } from '
|
|
38
|
+
import { FormValidator } from '../../../stores/forms';
|
|
39
39
|
|
|
40
40
|
// Define your form data structure
|
|
41
41
|
interface MyFormData {
|
|
@@ -196,7 +196,7 @@ The system maintains a global registry of all forms for cross-component access.
|
|
|
196
196
|
Retrieve a form instance by its identifier:
|
|
197
197
|
|
|
198
198
|
\`\`\`typescript
|
|
199
|
-
import { getFormById } from '
|
|
199
|
+
import { getFormById } from '../../../stores/forms';
|
|
200
200
|
|
|
201
201
|
const form = getFormById('my-form-id');
|
|
202
202
|
if (form) {
|
|
@@ -209,7 +209,7 @@ if (form) {
|
|
|
209
209
|
Check if a specific form is valid:
|
|
210
210
|
|
|
211
211
|
\`\`\`typescript
|
|
212
|
-
import { isFormValid } from '
|
|
212
|
+
import { isFormValid } from '../../../stores/forms';
|
|
213
213
|
|
|
214
214
|
if (isFormValid('my-form-id')) {
|
|
215
215
|
console.log('Form is valid!');
|
|
@@ -221,7 +221,7 @@ if (isFormValid('my-form-id')) {
|
|
|
221
221
|
Get error messages for a specific form:
|
|
222
222
|
|
|
223
223
|
\`\`\`typescript
|
|
224
|
-
import { getFormErrors } from '
|
|
224
|
+
import { getFormErrors } from '../../../stores/forms';
|
|
225
225
|
|
|
226
226
|
const errors = getFormErrors('my-form-id');
|
|
227
227
|
console.log(errors);
|
|
@@ -232,7 +232,7 @@ console.log(errors);
|
|
|
232
232
|
Reset a form by its identifier:
|
|
233
233
|
|
|
234
234
|
\`\`\`typescript
|
|
235
|
-
import { resetForm } from '
|
|
235
|
+
import { resetForm } from '../../../stores/forms';
|
|
236
236
|
|
|
237
237
|
resetForm('my-form-id');
|
|
238
238
|
\`\`\`
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks if the app mode is design and shows error notification if not.
|
|
3
|
+
*/
|
|
4
|
+
export const checkIfAppModeIsDesign = async () => {
|
|
5
|
+
const capabilities = await webflow.canForAppMode([
|
|
6
|
+
webflow.appModes.canDesign,
|
|
7
|
+
webflow.appModes.canEdit
|
|
8
|
+
]);
|
|
9
|
+
if (capabilities.canDesign) {
|
|
10
|
+
// Proceed with the action
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
// Provide feedback to the user
|
|
14
|
+
await webflow.notify({
|
|
15
|
+
type: 'Error',
|
|
16
|
+
message: 'This action cannot be performed right now. Ensure you are working in the Primary Locale, on the Main Branch, and in design mode.'
|
|
17
|
+
});
|
|
18
|
+
return false;
|
|
19
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { XSCPMetadata } from '../../../../types';
|
|
2
|
+
/**
|
|
3
|
+
* Processes pasted component data to validate Finsweet components.
|
|
4
|
+
*/
|
|
5
|
+
export declare const processPastedComponent: (pasteData: XSCPMetadata, component: string) => {
|
|
6
|
+
data: XSCPMetadata;
|
|
7
|
+
key: string;
|
|
8
|
+
} | undefined;
|
|
9
|
+
/**
|
|
10
|
+
* Handles pasting of Webflow components from clipboard.
|
|
11
|
+
*/
|
|
12
|
+
export declare const handlePasteXSCP: (e: ClipboardEvent, component: string) => {
|
|
13
|
+
data: XSCPMetadata;
|
|
14
|
+
key: string;
|
|
15
|
+
} | undefined;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { getLogger } from '../../../../utils/logger';
|
|
2
|
+
const logger = getLogger('webflow-apps-ui-utils');
|
|
3
|
+
/**
|
|
4
|
+
* Processes pasted component data to validate Finsweet components.
|
|
5
|
+
*/
|
|
6
|
+
export const processPastedComponent = (pasteData, component) => {
|
|
7
|
+
const valid = pasteData?.payload?.nodes?.some((node) => node?.data?.xattr?.some((attr) => {
|
|
8
|
+
if (component === 'consent') {
|
|
9
|
+
// consent is kinda different
|
|
10
|
+
const bannerFound = attr.name.includes(`fs-consent-element`) && attr.value === 'banner';
|
|
11
|
+
const wrapperFound = attr.name.includes(`fs-consent-element`) && attr.value === 'wrapper';
|
|
12
|
+
return bannerFound || wrapperFound;
|
|
13
|
+
}
|
|
14
|
+
return attr.name.includes(`fs-${component}-instance`);
|
|
15
|
+
}));
|
|
16
|
+
if (valid) {
|
|
17
|
+
return { data: pasteData, key: component };
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Handles pasting of Webflow components from clipboard.
|
|
22
|
+
*/
|
|
23
|
+
export const handlePasteXSCP = (e, component) => {
|
|
24
|
+
if (!e.clipboardData?.types.includes('application/json'))
|
|
25
|
+
return;
|
|
26
|
+
const data = e.clipboardData?.getData('application/json');
|
|
27
|
+
const clipboard = JSON.parse(data);
|
|
28
|
+
if (clipboard?.type === '@webflow/XscpData') {
|
|
29
|
+
try {
|
|
30
|
+
const data = e.clipboardData.getData('application/json');
|
|
31
|
+
const clipboard = JSON.parse(data);
|
|
32
|
+
if (clipboard?.type === '@webflow/XscpData') {
|
|
33
|
+
return processPastedComponent(clipboard, component);
|
|
34
|
+
}
|
|
35
|
+
webflow.notify({
|
|
36
|
+
type: 'Error',
|
|
37
|
+
message: 'Invalid! You can only paste valid Finsweet Components.'
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
logger.error({}, 'handlePasteXSCP', error);
|
|
42
|
+
webflow.notify({
|
|
43
|
+
type: 'Error',
|
|
44
|
+
message: 'Invalid! You can only paste valid Finsweet Components.'
|
|
45
|
+
});
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './handlePaste';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './handlePaste';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface AllAssets {
|
|
2
|
+
name: string;
|
|
3
|
+
url: string;
|
|
4
|
+
mimeType: string;
|
|
5
|
+
altText: string;
|
|
6
|
+
asset: Asset;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Gets all assets from the Webflow Canvas with their metadata.
|
|
10
|
+
*/
|
|
11
|
+
export declare const getAllAssets: () => Promise<AllAssets[]>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gets all assets from the Webflow Canvas with their metadata.
|
|
3
|
+
*/
|
|
4
|
+
export const getAllAssets = async () => {
|
|
5
|
+
const assets = await webflow.getAllAssets();
|
|
6
|
+
const assetPromises = assets.map(async (asset) => {
|
|
7
|
+
const url = await asset.getUrl();
|
|
8
|
+
const name = await asset.getName();
|
|
9
|
+
const mimeType = await asset.getMimeType();
|
|
10
|
+
const altText = (await asset.getAltText()) ?? '';
|
|
11
|
+
return {
|
|
12
|
+
name,
|
|
13
|
+
url,
|
|
14
|
+
mimeType,
|
|
15
|
+
altText,
|
|
16
|
+
asset
|
|
17
|
+
};
|
|
18
|
+
});
|
|
19
|
+
return await Promise.all(assetPromises);
|
|
20
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { COMPONENTS_CORE_SCRIPT, COMPONENTS_CORE_SCRIPT_LOCAL, COMPONENTS_SERVER_DEV_ENDPOINT, COMPONENTS_SERVER_DEV_ENDPOINT_LOCAL, COMPONENTS_SERVER_PROD_ENDPOINT, getLocalStorage } from '../../../utils';
|
|
2
|
+
const DEV_MODE_KEY = 'fsComponentsDevMode';
|
|
3
|
+
const DEV_MODE_SCRIPT_KEY = 'fsComponentsDevModeScript';
|
|
4
|
+
const DEV_MODE_API_KEY = 'fsComponentsDevModeApi';
|
|
5
|
+
const URL_DEV_MODE_KEY = 'dev';
|
|
6
|
+
const URL_DEV_MODE_SCRIPT_KEY = 'script';
|
|
7
|
+
const URL_DEV_MODE_API_KEY = 'api';
|
|
8
|
+
let lastLogTime = 0;
|
|
9
|
+
const LOG_THROTTLE_MS = 3000;
|
|
10
|
+
/**
|
|
11
|
+
* Gets a parameter value from the URL search params.
|
|
12
|
+
*/
|
|
13
|
+
const getUrlParam = (key) => {
|
|
14
|
+
if (typeof window === 'undefined')
|
|
15
|
+
return null;
|
|
16
|
+
try {
|
|
17
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
18
|
+
return urlParams.get(key);
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
console.error('Error getting URL parameter:', e);
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Throttled console log that only logs once within the throttle interval.
|
|
27
|
+
*/
|
|
28
|
+
const throttledLog = (message, data) => {
|
|
29
|
+
const now = Date.now();
|
|
30
|
+
if (now - lastLogTime >= LOG_THROTTLE_MS) {
|
|
31
|
+
console.log(message, data || '');
|
|
32
|
+
lastLogTime = now;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Gets the Finsweet components environment configuration.
|
|
37
|
+
*/
|
|
38
|
+
export const getFinsweetComponentsEnvironment = () => {
|
|
39
|
+
const devFromUrl = getUrlParam(URL_DEV_MODE_KEY);
|
|
40
|
+
const scriptFromUrl = getUrlParam(URL_DEV_MODE_SCRIPT_KEY);
|
|
41
|
+
const apiFromUrl = getUrlParam(URL_DEV_MODE_API_KEY);
|
|
42
|
+
const dev = devFromUrl !== null ? devFromUrl === 'true' : getLocalStorage(DEV_MODE_KEY) === 'true';
|
|
43
|
+
let script = scriptFromUrl || getLocalStorage(DEV_MODE_SCRIPT_KEY) || COMPONENTS_CORE_SCRIPT;
|
|
44
|
+
let api = dev
|
|
45
|
+
? apiFromUrl || getLocalStorage(DEV_MODE_API_KEY) || COMPONENTS_SERVER_DEV_ENDPOINT
|
|
46
|
+
: COMPONENTS_SERVER_PROD_ENDPOINT;
|
|
47
|
+
const isBrowser = typeof window !== 'undefined';
|
|
48
|
+
const isLocalhost = isBrowser && window?.location?.hostname?.includes('localhost');
|
|
49
|
+
//if localhost then use local scripts
|
|
50
|
+
if (isLocalhost) {
|
|
51
|
+
script = COMPONENTS_CORE_SCRIPT_LOCAL;
|
|
52
|
+
api = COMPONENTS_SERVER_DEV_ENDPOINT_LOCAL;
|
|
53
|
+
}
|
|
54
|
+
const development = !!dev || isLocalhost;
|
|
55
|
+
if (development) {
|
|
56
|
+
throttledLog(`\n\nFinsweet Components Environment:
|
|
57
|
+
- API: ${api}
|
|
58
|
+
- Core script: ${script}
|
|
59
|
+
- Development mode: ${development ? 'Yes' : 'No'}\n\n`);
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
development,
|
|
63
|
+
coreScript: script,
|
|
64
|
+
api
|
|
65
|
+
};
|
|
66
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inserts a template into the Designer Canvas using XSCP APIs.
|
|
3
|
+
*/
|
|
4
|
+
export const insertWithXSCP = async (template) => {
|
|
5
|
+
try {
|
|
6
|
+
// @ts-expect-error - typings not available for xscp
|
|
7
|
+
await webflow._internal.xscp(template);
|
|
8
|
+
}
|
|
9
|
+
catch (error) {
|
|
10
|
+
throw new Error(`Failed to insert template with XSCP: ${error}`);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AUTH0_AUDIENCE, AUTH0_CLIENT_ID, AUTH0_LOGIN_URL, AUTH0_REDIRECT_URL, AUTH0_SCOPE, PROD_FINSWEEET_ACCOUNTS_ORIGIN } from '
|
|
1
|
+
import { AUTH0_AUDIENCE, AUTH0_CLIENT_ID, AUTH0_LOGIN_URL, AUTH0_REDIRECT_URL, AUTH0_SCOPE, PROD_FINSWEEET_ACCOUNTS_ORIGIN } from '../../../utils/constants';
|
|
2
2
|
/**
|
|
3
3
|
* Opens a popup window for cross-window authentication with Auth0.
|
|
4
4
|
*/
|