@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.
Files changed (141) hide show
  1. package/dist/index.d.ts +0 -3
  2. package/dist/index.js +0 -3
  3. package/dist/types/customCode.d.ts +1 -1
  4. package/dist/ui/components/Loader.svelte +0 -2
  5. package/dist/ui/components/button/Button.stories.svelte +0 -8
  6. package/dist/ui/components/button/Button.svelte +68 -76
  7. package/dist/ui/components/button/index.d.ts +1 -1
  8. package/dist/ui/components/button/index.js +1 -0
  9. package/dist/ui/components/controlled-buttons/ControlledButtons.svelte +17 -7
  10. package/dist/ui/components/copy-text/CopyText.svelte +48 -39
  11. package/dist/ui/components/divider/Divider.stories.svelte +0 -4
  12. package/dist/ui/components/input/index.d.ts +1 -1
  13. package/dist/ui/components/input/index.js +1 -0
  14. package/dist/ui/components/layout/Layout.svelte +0 -6
  15. package/dist/ui/components/layout/common/EditModeMessage.svelte +1 -1
  16. package/dist/ui/components/layout/examples/ExampleLayout.svelte +2 -9
  17. package/dist/ui/components/layout/examples/Wrapper.svelte +1 -1
  18. package/dist/ui/components/modal/Example.svelte +0 -7
  19. package/dist/ui/components/modal/Modal.stories.svelte +0 -2
  20. package/dist/ui/components/modal/Modal.svelte +1 -1
  21. package/dist/ui/components/notification/Notification.stories.svelte +0 -8
  22. package/dist/ui/components/section/Section.stories.svelte +0 -1
  23. package/dist/ui/components/text/README.md +0 -2
  24. package/dist/ui/components/text/Text.stories.svelte +0 -6
  25. package/dist/ui/components/text/Text.svelte +0 -19
  26. package/dist/ui/components/tooltip/Tooltip.svelte +9 -4
  27. package/dist/ui/icons/FinsweetLogoOutlineIcon.svelte +17 -0
  28. package/dist/ui/icons/FinsweetLogoOutlineIcon.svelte.d.ts +26 -0
  29. package/dist/ui/icons/TriangleDownIconToggle.svelte +0 -1
  30. package/dist/ui/icons/index.d.ts +2 -1
  31. package/dist/ui/icons/index.js +2 -1
  32. package/dist/ui/index.css +1 -1
  33. package/dist/ui/index.d.ts +4 -0
  34. package/dist/ui/index.js +4 -0
  35. package/dist/{providers → ui/providers}/GlobalProvider.stories.js +7 -7
  36. package/dist/{providers → ui/providers}/GlobalProvider.svelte +1 -1
  37. package/dist/{providers → ui/providers}/GlobalProviderDemo.svelte +0 -2
  38. package/dist/{router → ui/router}/Router.stories.js +7 -7
  39. package/dist/{router → ui/router}/examples/RouterExample.svelte +0 -9
  40. package/dist/{router → ui/router}/examples/pages/AboutPage.svelte +0 -4
  41. package/dist/{router → ui/router}/providers/Link.svelte +0 -2
  42. package/dist/{router → ui/router}/providers/Route.svelte +0 -3
  43. package/dist/{router → ui/router}/providers/RouterProvider.svelte +1 -1
  44. package/dist/{router → ui/router}/router.svelte.d.ts +0 -1
  45. package/dist/{router → ui/router}/router.svelte.js +3 -2
  46. package/dist/ui/stores/form.d.ts +280 -0
  47. package/dist/{stores/forms.js → ui/stores/form.js} +310 -2
  48. package/dist/{stores → ui/stores}/forms/Form.stories.js +5 -5
  49. package/dist/{stores → ui/stores}/forms/FormDemo.svelte +1 -1
  50. package/dist/{stores → ui/stores}/index.d.ts +1 -3
  51. package/dist/{stores → ui/stores}/index.js +1 -3
  52. package/dist/ui/utils/api/checkIfAppModeIsDesign.d.ts +4 -0
  53. package/dist/ui/utils/api/checkIfAppModeIsDesign.js +19 -0
  54. package/dist/ui/utils/api/clipboard/handlePaste.d.ts +15 -0
  55. package/dist/ui/utils/api/clipboard/handlePaste.js +49 -0
  56. package/dist/ui/utils/api/clipboard/index.d.ts +1 -0
  57. package/dist/ui/utils/api/clipboard/index.js +1 -0
  58. package/dist/ui/utils/api/getAllAssets.d.ts +11 -0
  59. package/dist/ui/utils/api/getAllAssets.js +20 -0
  60. package/dist/ui/utils/api/getFinsweetComponentsEnvironment.d.ts +8 -0
  61. package/dist/ui/utils/api/getFinsweetComponentsEnvironment.js +66 -0
  62. package/dist/ui/utils/api/index.d.ts +5 -0
  63. package/dist/ui/utils/api/index.js +5 -0
  64. package/dist/ui/utils/api/insertWithXSCP.d.ts +4 -0
  65. package/dist/ui/utils/api/insertWithXSCP.js +12 -0
  66. package/dist/{utils → ui/utils}/auth/crossWindowLogin.d.ts +1 -1
  67. package/dist/{utils → ui/utils}/auth/crossWindowLogin.js +1 -1
  68. package/dist/{utils → ui/utils}/auth/index.d.ts +1 -1
  69. package/dist/{utils → ui/utils}/auth/index.js +4 -6
  70. package/dist/{utils → ui/utils}/diff-mapper/DiffMapper.stories.js +2 -2
  71. package/dist/{utils → ui/utils}/diff-mapper/DiffMapperDemo.svelte +1 -1
  72. package/dist/{utils → ui/utils}/helpers/goto.js +2 -4
  73. package/dist/ui/utils/helpers/index.d.ts +1 -0
  74. package/dist/ui/utils/helpers/index.js +1 -0
  75. package/dist/ui/utils/index.d.ts +3 -0
  76. package/dist/ui/utils/index.js +3 -0
  77. package/dist/utils/custom-code/api.d.ts +1 -1
  78. package/dist/utils/custom-code/api.js +4 -6
  79. package/dist/utils/custom-code/configs.d.ts +3 -2
  80. package/dist/utils/custom-code/configs.js +5 -8
  81. package/dist/utils/custom-code/index.d.ts +1 -1
  82. package/dist/utils/custom-code/index.js +1 -1
  83. package/dist/utils/helpers/index.d.ts +0 -1
  84. package/dist/utils/helpers/index.js +0 -1
  85. package/dist/utils/index.d.ts +2 -3
  86. package/dist/utils/index.js +1 -3
  87. package/dist/utils/logger/index.d.ts +1 -2
  88. package/dist/utils/stores/index.d.ts +2 -0
  89. package/dist/utils/stores/index.js +2 -0
  90. package/package.json +13 -4
  91. package/dist/stores/forms.d.ts +0 -147
  92. /package/dist/{providers → ui/providers}/GlobalProvider.stories.d.ts +0 -0
  93. /package/dist/{providers → ui/providers}/GlobalProvider.svelte.d.ts +0 -0
  94. /package/dist/{providers → ui/providers}/GlobalProviderDemo.svelte.d.ts +0 -0
  95. /package/dist/{providers → ui/providers}/configuratorUtils.d.ts +0 -0
  96. /package/dist/{providers → ui/providers}/configuratorUtils.js +0 -0
  97. /package/dist/{providers → ui/providers}/globalContext.svelte.d.ts +0 -0
  98. /package/dist/{providers → ui/providers}/globalContext.svelte.js +0 -0
  99. /package/dist/{providers → ui/providers}/index.d.ts +0 -0
  100. /package/dist/{providers → ui/providers}/index.js +0 -0
  101. /package/dist/{providers → ui/providers}/types.d.ts +0 -0
  102. /package/dist/{providers → ui/providers}/types.js +0 -0
  103. /package/dist/{router → ui/router}/Router.stories.d.ts +0 -0
  104. /package/dist/{router → ui/router}/examples/RouterExample.svelte.d.ts +0 -0
  105. /package/dist/{router → ui/router}/examples/index.d.ts +0 -0
  106. /package/dist/{router → ui/router}/examples/index.js +0 -0
  107. /package/dist/{router → ui/router}/examples/pages/AboutPage.svelte.d.ts +0 -0
  108. /package/dist/{router → ui/router}/examples/pages/HomePage.svelte +0 -0
  109. /package/dist/{router → ui/router}/examples/pages/HomePage.svelte.d.ts +0 -0
  110. /package/dist/{router → ui/router}/examples/pages/NotFoundPage.svelte +0 -0
  111. /package/dist/{router → ui/router}/examples/pages/NotFoundPage.svelte.d.ts +0 -0
  112. /package/dist/{router → ui/router}/hooks.svelte.d.ts +0 -0
  113. /package/dist/{router → ui/router}/hooks.svelte.js +0 -0
  114. /package/dist/{router → ui/router}/index.d.ts +0 -0
  115. /package/dist/{router → ui/router}/index.js +0 -0
  116. /package/dist/{router → ui/router}/providers/Link.svelte.d.ts +0 -0
  117. /package/dist/{router → ui/router}/providers/Route.svelte.d.ts +0 -0
  118. /package/dist/{router → ui/router}/providers/RouterProvider.svelte.d.ts +0 -0
  119. /package/dist/{router → ui/router}/providers/index.d.ts +0 -0
  120. /package/dist/{router → ui/router}/providers/index.js +0 -0
  121. /package/dist/{stores → ui/stores}/breakpoints.d.ts +0 -0
  122. /package/dist/{stores → ui/stores}/breakpoints.js +0 -0
  123. /package/dist/{stores → ui/stores}/componentInjectErrors.d.ts +0 -0
  124. /package/dist/{stores → ui/stores}/componentInjectErrors.js +0 -0
  125. /package/dist/{stores → ui/stores}/forms/Form.stories.d.ts +0 -0
  126. /package/dist/{stores → ui/stores}/forms/FormDemo.svelte.d.ts +0 -0
  127. /package/dist/{stores → ui/stores}/showConfirmActionModal.d.ts +0 -0
  128. /package/dist/{stores → ui/stores}/showConfirmActionModal.js +0 -0
  129. /package/dist/{stores → ui/stores}/siteInfo.d.ts +0 -0
  130. /package/dist/{stores → ui/stores}/siteInfo.js +0 -0
  131. /package/dist/{utils → ui/utils}/diff-mapper/DiffMapper.stories.d.ts +0 -0
  132. /package/dist/{utils → ui/utils}/diff-mapper/DiffMapperDemo.svelte.d.ts +0 -0
  133. /package/dist/{utils → ui/utils}/diff-mapper/deepDiffMapper.d.ts +0 -0
  134. /package/dist/{utils → ui/utils}/diff-mapper/deepDiffMapper.js +0 -0
  135. /package/dist/{utils → ui/utils}/diff-mapper/index.d.ts +0 -0
  136. /package/dist/{utils → ui/utils}/diff-mapper/index.js +0 -0
  137. /package/dist/{utils → ui/utils}/helpers/goto.d.ts +0 -0
  138. /package/dist/{stores → utils/stores}/isPreviewMode.d.ts +0 -0
  139. /package/dist/{stores → utils/stores}/isPreviewMode.js +0 -0
  140. /package/dist/{stores → utils/stores}/router.d.ts +0 -0
  141. /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 (for legacy compatibility)
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
  \`\`\`
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { FormValidator } from '../forms';
2
+ import { FormValidator } from '../form';
3
3
 
4
4
  interface DemoFormData {
5
5
  name: string;
@@ -1,7 +1,5 @@
1
1
  export * from './breakpoints';
2
- export * from './forms';
2
+ export * from './form';
3
3
  export * from './componentInjectErrors';
4
- export * from './isPreviewMode';
5
- export * from './router';
6
4
  export * from './showConfirmActionModal';
7
5
  export * from './siteInfo';
@@ -1,7 +1,5 @@
1
1
  export * from './breakpoints';
2
- export * from './forms';
2
+ export * from './form';
3
3
  export * from './componentInjectErrors';
4
- export * from './isPreviewMode';
5
- export * from './router';
6
4
  export * from './showConfirmActionModal';
7
5
  export * from './siteInfo';
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Checks if the app mode is design and shows error notification if not.
3
+ */
4
+ export declare const checkIfAppModeIsDesign: () => Promise<boolean>;
@@ -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,8 @@
1
+ /**
2
+ * Gets the Finsweet components environment configuration.
3
+ */
4
+ export declare const getFinsweetComponentsEnvironment: () => {
5
+ development: boolean;
6
+ coreScript: string;
7
+ api: string;
8
+ };
@@ -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,5 @@
1
+ export * from './checkIfAppModeIsDesign';
2
+ export * from './clipboard';
3
+ export * from './getAllAssets';
4
+ export * from './getFinsweetComponentsEnvironment';
5
+ export * from './insertWithXSCP';
@@ -0,0 +1,5 @@
1
+ export * from './checkIfAppModeIsDesign';
2
+ export * from './clipboard';
3
+ export * from './getAllAssets';
4
+ export * from './getFinsweetComponentsEnvironment';
5
+ export * from './insertWithXSCP';
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Inserts a template into the Designer Canvas using XSCP APIs.
3
+ */
4
+ export declare const insertWithXSCP: (template: string) => Promise<void>;
@@ -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 type { FinsweetAuth } from '../../types';
1
+ import type { FinsweetAuth } from '../../../types';
2
2
  /**
3
3
  * Opens a popup window for cross-window authentication with Auth0.
4
4
  */
@@ -1,4 +1,4 @@
1
- import { AUTH0_AUDIENCE, AUTH0_CLIENT_ID, AUTH0_LOGIN_URL, AUTH0_REDIRECT_URL, AUTH0_SCOPE, PROD_FINSWEEET_ACCOUNTS_ORIGIN } from '../constants';
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
  */
@@ -1,4 +1,4 @@
1
- import type { FinsweetAuth } from '../../types';
1
+ import type { FinsweetAuth } from '../../../types';
2
2
  /**
3
3
  * Store for the Finsweet user data.
4
4
  */