@finsweet/webflow-apps-utils 1.0.4 → 1.0.5

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 (35) hide show
  1. package/dist/providers/GlobalProvider.stories.d.ts +5 -0
  2. package/dist/providers/GlobalProvider.stories.js +419 -0
  3. package/dist/providers/GlobalProviderDemo.svelte +266 -0
  4. package/dist/providers/GlobalProviderDemo.svelte.d.ts +3 -0
  5. package/dist/providers/configuratorUtils.d.ts +11 -14
  6. package/dist/providers/configuratorUtils.js +68 -115
  7. package/dist/providers/index.d.ts +1 -1
  8. package/dist/providers/index.js +1 -1
  9. package/dist/router/Router.stories.js +519 -2
  10. package/dist/stores/forms/Form.stories.d.ts +5 -0
  11. package/dist/stores/forms/Form.stories.js +342 -0
  12. package/dist/stores/forms/FormDemo.svelte +545 -0
  13. package/dist/stores/forms/FormDemo.svelte.d.ts +18 -0
  14. package/dist/ui/components/button/Button.svelte +1 -1
  15. package/dist/ui/components/copy-text/CopyText.stories.js +1 -1
  16. package/dist/ui/components/copy-text/CopyText.svelte +17 -19
  17. package/dist/ui/components/layout/Layout.svelte +38 -5
  18. package/dist/ui/components/layout/Layout.svelte.d.ts +24 -1
  19. package/dist/ui/components/layout/examples/ExampleLayout.svelte +12 -12
  20. package/dist/ui/components/section/Section.svelte +4 -2
  21. package/dist/ui/index.css +6 -2
  22. package/dist/utils/diff-mapper/DiffMapper.stories.d.ts +5 -0
  23. package/dist/utils/diff-mapper/DiffMapper.stories.js +185 -0
  24. package/dist/utils/diff-mapper/DiffMapperDemo.svelte +351 -0
  25. package/dist/utils/diff-mapper/DiffMapperDemo.svelte.d.ts +18 -0
  26. package/dist/utils/diff-mapper/deepDiffMapper.d.ts +31 -0
  27. package/dist/utils/diff-mapper/deepDiffMapper.js +264 -0
  28. package/dist/utils/diff-mapper/index.d.ts +1 -0
  29. package/dist/utils/diff-mapper/index.js +1 -0
  30. package/dist/utils/index.d.ts +1 -0
  31. package/dist/utils/index.js +1 -0
  32. package/package.json +1 -1
  33. package/dist/providers/GlobalProvider.mdx +0 -322
  34. package/dist/router/Router.mdx +0 -958
  35. package/dist/stores/docs/Form.mdx +0 -542
@@ -0,0 +1,351 @@
1
+ <script lang="ts">
2
+ import { getDetailedDiff, hasChangesViaDiff } from '../../providers/configuratorUtils';
3
+
4
+ let output = '';
5
+ let coercionExamples = '';
6
+ let performanceResults = '';
7
+ let deepOutput = '';
8
+
9
+ function runDiffExamples() {
10
+ // Example 1: Basic object comparison
11
+ const obj1 = { name: 'John', age: 30, active: 'true' };
12
+ const obj2 = { name: 'John', age: 31, active: true };
13
+
14
+ // @ts-expect-error - intentional type mismatch for demo
15
+ const hasChanges = hasChangesViaDiff(obj1, obj2);
16
+ // @ts-expect-error - intentional type mismatch for demo
17
+ const detailedDiff = getDetailedDiff(obj1, obj2);
18
+
19
+ // Example 2: Nested object comparison
20
+ const nested1 = {
21
+ user: { profile: { name: 'John', settings: { theme: 'dark' } } },
22
+ items: [{ id: 1, name: 'Item 1' }]
23
+ };
24
+
25
+ const nested2 = {
26
+ user: { profile: { name: 'John', settings: { theme: 'light' } } },
27
+ items: [
28
+ { id: 1, name: 'Item 1' },
29
+ { id: 2, name: 'Item 2' }
30
+ ]
31
+ };
32
+
33
+ const nestedChanges = hasChangesViaDiff(nested1, nested2);
34
+ const nestedDiff = getDetailedDiff(nested1, nested2);
35
+
36
+ // Example 3: Type coercion examples
37
+ const coercion1 = { count: '5', enabled: '', value: '0' };
38
+ const coercion2 = { count: 5, enabled: false, value: 0 };
39
+ // @ts-expect-error - intentional type mismatch for demo
40
+ const coercionChanges = hasChangesViaDiff(coercion1, coercion2);
41
+
42
+ output = `
43
+ 📊 DIFF MAPPER RESULTS
44
+
45
+ 1. Basic Object Comparison:
46
+ Object 1: ${JSON.stringify(obj1)}
47
+ Object 2: ${JSON.stringify(obj2)}
48
+ Has Changes: ${hasChanges}
49
+ Age changed from 30 to 31, but 'true' === true (type coercion)
50
+
51
+ 2. Nested Object Comparison:
52
+ Has Changes: ${nestedChanges}
53
+ Theme changed from 'dark' to 'light'
54
+ New item added to array
55
+
56
+ 3. Type Coercion Examples:
57
+ Object 1: ${JSON.stringify(coercion1)}
58
+ Object 2: ${JSON.stringify(coercion2)}
59
+ Has Changes: ${coercionChanges}
60
+ All values are equivalent due to intelligent type coercion!
61
+
62
+ 📝 Check the browser console for detailed diff objects.
63
+ `;
64
+
65
+ // Log detailed results to console
66
+ console.group('🔍 DiffMapper Detailed Results');
67
+ console.log('Basic diff:', detailedDiff);
68
+ console.log('Nested diff:', nestedDiff);
69
+ console.groupEnd();
70
+ }
71
+
72
+ function generateCoercionExamples() {
73
+ const examples = [
74
+ [{ value: '42' }, { value: 42 }, 'String "42" equals number 42'],
75
+ [{ active: 'true' }, { active: true }, 'String "true" equals boolean true'],
76
+ [{ empty: '' }, { empty: 0 }, 'Empty string equals number 0'],
77
+ [{ name: 'John' }, { name: ' John ' }, 'Whitespace is trimmed'],
78
+ [{ enabled: 'false' }, { enabled: false }, 'String "false" equals boolean false']
79
+ ];
80
+
81
+ let html = '<div style="display: grid; gap: 10px;">';
82
+
83
+ examples.forEach(([obj1, obj2, description]) => {
84
+ const hasChanges = hasChangesViaDiff(obj1, obj2);
85
+ const status = hasChanges ? '❌ DIFFERENT' : '✅ SAME';
86
+ const color = hasChanges ? 'var(--redText)' : 'var(--greenText)';
87
+
88
+ html += `
89
+ <div class="coercion-example">
90
+ <div style="color: ${color}; font-weight: var(--font-weight-medium); margin-bottom: var(--spacing-4);">${status}</div>
91
+ <div style="color: var(--text1);"><strong>A:</strong> ${JSON.stringify(obj1)}</div>
92
+ <div style="color: var(--text1);"><strong>B:</strong> ${JSON.stringify(obj2)}</div>
93
+ <div style="color: var(--text3); font-style: italic; margin-top: var(--spacing-4);">${description}</div>
94
+ </div>
95
+ `;
96
+ });
97
+
98
+ html += '</div>';
99
+ coercionExamples = html;
100
+ }
101
+
102
+ function runPerformanceTest() {
103
+ // Create large objects for testing
104
+ const createLargeObject = (size: number) => {
105
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
106
+ const obj: Record<string, any> = {};
107
+ for (let i = 0; i < size; i++) {
108
+ obj[`key${i}`] = {
109
+ id: i,
110
+ name: `Item ${i}`,
111
+ nested: {
112
+ value: Math.random(),
113
+ timestamp: Date.now()
114
+ }
115
+ };
116
+ }
117
+ return obj;
118
+ };
119
+
120
+ const obj1 = createLargeObject(100);
121
+ const obj2 = { ...obj1 };
122
+ obj2.key50.nested.value = 999; // Change one value
123
+
124
+ // Measure performance
125
+ const start = performance.now();
126
+ const hasChanges = hasChangesViaDiff(obj1, obj2);
127
+ const end = performance.now();
128
+
129
+ // Test caching - second call should be faster
130
+ const start2 = performance.now();
131
+ hasChangesViaDiff(obj1, obj2);
132
+ const end2 = performance.now();
133
+
134
+ performanceResults = `
135
+ <div class="performance-result">
136
+ <h4>Performance Test Results</h4>
137
+ <div><strong>Object Size:</strong> 100 nested objects</div>
138
+ <div><strong>Change Detected:</strong> ${hasChanges ? 'Yes' : 'No'}</div>
139
+ <div><strong>First Call:</strong> ${(end - start).toFixed(2)}ms</div>
140
+ <div><strong>Cached Call:</strong> ${(end2 - start2).toFixed(2)}ms</div>
141
+ <div style="color: var(--greenText); margin-top: var(--spacing-12); font-weight: var(--font-weight-medium);">
142
+ ⚡ ${((end - start) / (end2 - start2)).toFixed(1)}x faster with caching!
143
+ </div>
144
+ </div>
145
+ `;
146
+
147
+ console.log('Performance test completed:', {
148
+ hasChanges,
149
+ firstCall: end - start,
150
+ cachedCall: end2 - start2,
151
+ speedup: (end - start) / (end2 - start2)
152
+ });
153
+ }
154
+
155
+ function runDeepTest() {
156
+ const deepObj1 = {
157
+ level1: {
158
+ level2: {
159
+ level3: {
160
+ level4: {
161
+ data: 'original',
162
+ count: 42,
163
+ items: [1, 2, 3]
164
+ }
165
+ }
166
+ }
167
+ }
168
+ };
169
+
170
+ const deepObj2 = {
171
+ level1: {
172
+ level2: {
173
+ level3: {
174
+ level4: {
175
+ data: 'modified',
176
+ count: 42,
177
+ items: [1, 2, 3, 4]
178
+ }
179
+ }
180
+ }
181
+ }
182
+ };
183
+
184
+ const hasChanges = hasChangesViaDiff(deepObj1, deepObj2);
185
+ const diff = getDetailedDiff(deepObj1, deepObj2);
186
+
187
+ deepOutput = `
188
+ Deep Object Comparison Results:
189
+
190
+ Has Changes: ${hasChanges}
191
+
192
+ Changes detected:
193
+ - level1.level2.level3.level4.data: "original" → "modified"
194
+ - level1.level2.level3.level4.items: Array length changed (3 → 4)
195
+
196
+ The diff mapper successfully navigated 4 levels deep to detect specific changes!
197
+
198
+ See console for the complete diff structure.
199
+ `;
200
+
201
+ console.log('Deep nesting diff:', diff);
202
+ }
203
+ </script>
204
+
205
+ <div class="demo-container">
206
+ <h3>DiffMapper Utilities Demo</h3>
207
+ <p>Open the browser console to see diff results.</p>
208
+ <button class="btn btn--primary" onclick={runDiffExamples}>Run Diff Examples</button>
209
+ <pre class="output-block">{output}</pre>
210
+
211
+ <hr class="divider" />
212
+
213
+ <h3>Type Coercion Examples</h3>
214
+ <button class="btn btn--primary" onclick={generateCoercionExamples}>Show Type Coercion</button>
215
+ <div class="examples-container">
216
+ {@html coercionExamples}
217
+ </div>
218
+
219
+ <hr class="divider" />
220
+
221
+ <h3>Performance Features</h3>
222
+ <button class="btn btn--primary" onclick={runPerformanceTest}>Run Performance Test</button>
223
+ <div class="results-container">
224
+ {@html performanceResults}
225
+ </div>
226
+
227
+ <hr class="divider" />
228
+
229
+ <h3>Deep Nesting Comparison</h3>
230
+ <button class="btn btn--primary" onclick={runDeepTest}>Test Deep Objects</button>
231
+ <pre class="output-block small">{deepOutput}</pre>
232
+ </div>
233
+
234
+ <style>
235
+ .demo-container {
236
+ padding: var(--spacing-24);
237
+ font-family: var(--font-stack);
238
+ background: var(--background1);
239
+ color: var(--text1);
240
+ max-width: 800px;
241
+ margin: 0 auto;
242
+ }
243
+
244
+ h3 {
245
+ color: var(--text1);
246
+ font-size: var(--font-size-large);
247
+ font-weight: var(--font-weight-medium);
248
+ margin: var(--spacing-16) 0 var(--spacing-8) 0;
249
+ }
250
+
251
+ p {
252
+ color: var(--text2);
253
+ font-size: var(--font-size-small);
254
+ margin: var(--spacing-8) 0;
255
+ }
256
+
257
+ .btn {
258
+ padding: var(--padding-small) var(--padding-regular);
259
+ border: none;
260
+ border-radius: var(--border-radius);
261
+ cursor: pointer;
262
+ font-size: var(--font-size-small);
263
+ font-weight: var(--font-weight-normal);
264
+ font-family: var(--font-stack);
265
+ transition: all 0.2s ease;
266
+ margin: var(--spacing-8) 0;
267
+ }
268
+
269
+ .btn--primary {
270
+ background: var(--actionPrimaryBackground);
271
+ color: var(--actionPrimaryText);
272
+ box-shadow: var(--boxShadows-action-colored);
273
+ }
274
+
275
+ .btn--primary:hover {
276
+ background: var(--actionPrimaryBackgroundHover);
277
+ color: var(--actionPrimaryTextHover);
278
+ }
279
+
280
+ .output-block {
281
+ background: var(--background2);
282
+ border: 1px solid var(--border2);
283
+ padding: var(--spacing-12);
284
+ margin-top: var(--spacing-12);
285
+ white-space: pre-wrap;
286
+ border-radius: var(--border-radius);
287
+ font-family: monospace;
288
+ color: var(--text2);
289
+ font-size: var(--font-size-small);
290
+ }
291
+
292
+ .output-block.small {
293
+ font-size: var(--font-size-small);
294
+ }
295
+
296
+ .divider {
297
+ border: none;
298
+ border-top: 1px solid var(--border2);
299
+ margin: var(--spacing-32) 0;
300
+ }
301
+
302
+ .examples-container,
303
+ .results-container {
304
+ margin-top: var(--spacing-16);
305
+ }
306
+
307
+ /* Dynamic HTML content styling */
308
+ :global(.examples-container > div) {
309
+ display: grid;
310
+ gap: var(--spacing-12);
311
+ }
312
+
313
+ :global(.coercion-example) {
314
+ padding: var(--spacing-12);
315
+ border: 1px solid var(--border2);
316
+ border-radius: var(--border-radius);
317
+ background: var(--background2);
318
+ }
319
+
320
+ :global(.performance-result) {
321
+ background: var(--background2);
322
+ border: 1px solid var(--border2);
323
+ padding: var(--spacing-16);
324
+ border-radius: var(--border-radius);
325
+ }
326
+
327
+ :global(.results-container > div) {
328
+ background: var(--background2);
329
+ border: 1px solid var(--border2);
330
+ padding: var(--spacing-16);
331
+ border-radius: var(--border-radius);
332
+ }
333
+
334
+ :global(.results-container h4) {
335
+ color: var(--text1);
336
+ font-size: var(--font-size-large);
337
+ font-weight: var(--font-weight-medium);
338
+ margin: 0 0 var(--spacing-8) 0;
339
+ }
340
+
341
+ :global(.results-container div) {
342
+ color: var(--text2);
343
+ font-size: var(--font-size-small);
344
+ margin: var(--spacing-4) 0;
345
+ }
346
+
347
+ :global(.results-container strong) {
348
+ color: var(--text1);
349
+ font-weight: var(--font-weight-medium);
350
+ }
351
+ </style>
@@ -0,0 +1,18 @@
1
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
+ $$bindings?: Bindings;
4
+ } & Exports;
5
+ (internal: unknown, props: {
6
+ $$events?: Events;
7
+ $$slots?: Slots;
8
+ }): Exports & {
9
+ $set?: any;
10
+ $on?: any;
11
+ };
12
+ z_$$bindings?: Bindings;
13
+ }
14
+ declare const DiffMapperDemo: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
+ [evt: string]: CustomEvent<any>;
16
+ }, {}, {}, string>;
17
+ type DiffMapperDemo = InstanceType<typeof DiffMapperDemo>;
18
+ export default DiffMapperDemo;
@@ -0,0 +1,31 @@
1
+ export declare enum DiffType {
2
+ CREATED = "created",
3
+ UPDATED = "updated",
4
+ DELETED = "deleted",
5
+ UNCHANGED = "unchanged"
6
+ }
7
+ export interface DiffResult {
8
+ type: DiffType;
9
+ data: any;
10
+ }
11
+ export type DiffMap = {
12
+ [key: string]: DiffResult | DiffMap;
13
+ };
14
+ export type ComparableValue = any;
15
+ declare class DeepDiffMapper {
16
+ private visitedPairs;
17
+ private isFunction;
18
+ private isArray;
19
+ private isDate;
20
+ private isObject;
21
+ private isValue;
22
+ private compareValues;
23
+ private areEquivalentPrimitives;
24
+ private hasCircularReference;
25
+ private _map;
26
+ map(obj1: ComparableValue, obj2: ComparableValue): DiffResult | DiffMap;
27
+ compare(obj1: ComparableValue, obj2: ComparableValue): DiffResult | DiffMap;
28
+ }
29
+ export declare const deepDiffMapper: DeepDiffMapper;
30
+ export { DeepDiffMapper };
31
+ export declare function compareObjects(obj1: ComparableValue, obj2: ComparableValue): DiffResult | DiffMap;
@@ -0,0 +1,264 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ export var DiffType;
3
+ (function (DiffType) {
4
+ DiffType["CREATED"] = "created";
5
+ DiffType["UPDATED"] = "updated";
6
+ DiffType["DELETED"] = "deleted";
7
+ DiffType["UNCHANGED"] = "unchanged";
8
+ })(DiffType || (DiffType = {}));
9
+ class DeepDiffMapper {
10
+ visitedPairs = new WeakMap();
11
+ isFunction(x) {
12
+ return Object.prototype.toString.call(x) === '[object Function]';
13
+ }
14
+ isArray(x) {
15
+ return Object.prototype.toString.call(x) === '[object Array]';
16
+ }
17
+ isDate(x) {
18
+ return Object.prototype.toString.call(x) === '[object Date]';
19
+ }
20
+ isObject(x) {
21
+ return Object.prototype.toString.call(x) === '[object Object]';
22
+ }
23
+ isValue(x) {
24
+ return !this.isObject(x) && !this.isArray(x);
25
+ }
26
+ compareValues(value1, value2) {
27
+ if (value1 === value2 || this.areEquivalentPrimitives(value1, value2)) {
28
+ return DiffType.UNCHANGED;
29
+ }
30
+ if (this.isDate(value1) && this.isDate(value2) && value1.getTime() === value2.getTime()) {
31
+ return DiffType.UNCHANGED;
32
+ }
33
+ if (value1 === undefined) {
34
+ return DiffType.CREATED;
35
+ }
36
+ if (value2 === undefined) {
37
+ return DiffType.DELETED;
38
+ }
39
+ return DiffType.UPDATED;
40
+ }
41
+ areEquivalentPrimitives(value1, value2) {
42
+ // Handle null and undefined
43
+ if (value1 == null && value2 == null) {
44
+ return true;
45
+ }
46
+ // Handle string trimming - if both are strings, compare trimmed versions
47
+ if (typeof value1 === 'string' && typeof value2 === 'string') {
48
+ return value1.trim() === value2.trim();
49
+ }
50
+ // Handle string/boolean coercion with specific string values
51
+ if ((typeof value1 === 'string' || typeof value1 === 'boolean') &&
52
+ (typeof value2 === 'string' || typeof value2 === 'boolean')) {
53
+ const val1 = typeof value1 === 'string' ? value1.trim().toLowerCase() : value1;
54
+ const val2 = typeof value2 === 'string' ? value2.trim().toLowerCase() : value2;
55
+ // Handle specific string-boolean equivalences
56
+ if (typeof val1 === 'string' && typeof val2 === 'boolean') {
57
+ if ((val1 === 'true' && val2 === true) || (val1 === 'false' && val2 === false)) {
58
+ return true;
59
+ }
60
+ // Handle empty string as false
61
+ if (val1 === '' && val2 === false) {
62
+ return true;
63
+ }
64
+ }
65
+ if (typeof val2 === 'string' && typeof val1 === 'boolean') {
66
+ if ((val2 === 'true' && val1 === true) || (val2 === 'false' && val1 === false)) {
67
+ return true;
68
+ }
69
+ // Handle empty string as false
70
+ if (val2 === '' && val1 === false) {
71
+ return true;
72
+ }
73
+ }
74
+ }
75
+ // Handle string/number coercion
76
+ if ((typeof value1 === 'string' || typeof value1 === 'number') &&
77
+ (typeof value2 === 'string' || typeof value2 === 'number')) {
78
+ // For string to number comparison, trim the string first
79
+ const str1 = typeof value1 === 'string' ? value1.trim() : value1.toString();
80
+ const str2 = typeof value2 === 'string' ? value2.trim() : value2.toString();
81
+ // Only treat truly empty strings (not whitespace-only) as equivalent to 0
82
+ // Distinguish between empty string ('') and whitespace-only strings (' ', ' ')
83
+ if (typeof value1 === 'string' && value1 === '' && typeof value2 === 'number') {
84
+ return value2 === 0;
85
+ }
86
+ if (typeof value2 === 'string' && value2 === '' && typeof value1 === 'number') {
87
+ return value1 === 0;
88
+ }
89
+ // Don't treat whitespace-only strings as equivalent to numbers
90
+ if (typeof value1 === 'string' && value1.trim() === '' && value1 !== '') {
91
+ return false; // ' ', ' ', etc. should not be equivalent to numbers
92
+ }
93
+ if (typeof value2 === 'string' && value2.trim() === '' && value2 !== '') {
94
+ return false; // ' ', ' ', etc. should not be equivalent to numbers
95
+ }
96
+ // Convert both to numbers and compare
97
+ const num1 = Number(str1);
98
+ const num2 = Number(str2);
99
+ // Handle NaN case specifically - only consider equal if both are exactly NaN
100
+ if (isNaN(num1) || isNaN(num2)) {
101
+ return Number.isNaN(value1) && Number.isNaN(value2);
102
+ }
103
+ if (!isNaN(num1) && !isNaN(num2)) {
104
+ return num1 === num2;
105
+ }
106
+ }
107
+ return false;
108
+ }
109
+ hasCircularReference(obj1, obj2) {
110
+ if (typeof obj1 !== 'object' || typeof obj2 !== 'object' || obj1 === null || obj2 === null) {
111
+ return false;
112
+ }
113
+ if (!this.visitedPairs.has(obj1)) {
114
+ this.visitedPairs.set(obj1, new WeakSet());
115
+ }
116
+ const visited = this.visitedPairs.get(obj1);
117
+ if (visited.has(obj2)) {
118
+ return true;
119
+ }
120
+ visited.add(obj2);
121
+ return false;
122
+ }
123
+ _map(obj1, obj2) {
124
+ if (this.isFunction(obj1) || this.isFunction(obj2)) {
125
+ throw new Error('Invalid argument. Function given, object expected.');
126
+ }
127
+ // Only treat as values if both are values, or if one is undefined and the other is also a value
128
+ // This prevents treating undefined vs complex object as a value comparison
129
+ if (this.isValue(obj1) && this.isValue(obj2)) {
130
+ const diffType = this.compareValues(obj1, obj2);
131
+ return {
132
+ type: diffType,
133
+ data: diffType === DiffType.CREATED ? obj2 : obj1
134
+ };
135
+ }
136
+ // Special case: one is undefined/null and the other is a complex object
137
+ if ((obj1 === undefined || obj1 === null) && (this.isArray(obj2) || this.isObject(obj2))) {
138
+ // Recursively process the non-undefined object as if it was created
139
+ return this._map({}, obj2);
140
+ }
141
+ if ((obj2 === undefined || obj2 === null) && (this.isArray(obj1) || this.isObject(obj1))) {
142
+ // Recursively process the non-undefined object as if it was deleted
143
+ return this._map(obj1, {});
144
+ }
145
+ // Handle primitive value cases (one or both are primitives but not both complex)
146
+ if (this.isValue(obj1) || this.isValue(obj2)) {
147
+ const diffType = this.compareValues(obj1, obj2);
148
+ return {
149
+ type: diffType,
150
+ data: diffType === DiffType.CREATED ? obj2 : obj1
151
+ };
152
+ }
153
+ // Check if both are complex types but of different specific types (array vs object)
154
+ const isArrayVsObject = (this.isArray(obj1) && this.isObject(obj2)) || (this.isObject(obj1) && this.isArray(obj2));
155
+ // For mixed types (array vs object), check if they have the same structure first
156
+ if (isArrayVsObject) {
157
+ // If both are empty, they're still different types
158
+ const keys1 = Object.keys(obj1 || {});
159
+ const keys2 = Object.keys(obj2 || {});
160
+ if (keys1.length === 0 && keys2.length === 0) {
161
+ return {
162
+ type: DiffType.UPDATED,
163
+ data: obj1
164
+ };
165
+ }
166
+ // Continue with property comparison for non-empty mixed types
167
+ }
168
+ // Check for circular references
169
+ if (this.hasCircularReference(obj1, obj2)) {
170
+ return {
171
+ type: DiffType.UNCHANGED,
172
+ data: obj1
173
+ };
174
+ }
175
+ const diff = {};
176
+ // Get all property keys including symbols and own properties
177
+ const getAllKeys = (obj) => {
178
+ if (!obj)
179
+ return [];
180
+ const keys = [...Object.keys(obj), ...Object.getOwnPropertySymbols(obj)];
181
+ // Include inherited enumerable properties
182
+ for (const key in obj) {
183
+ if (!keys.includes(key)) {
184
+ keys.push(key);
185
+ }
186
+ }
187
+ return keys;
188
+ };
189
+ const keys1 = getAllKeys(obj1);
190
+ const keys2 = getAllKeys(obj2);
191
+ const allKeys = new Set([...keys1, ...keys2]);
192
+ // Check if prototypes are different and add __proto__ to comparison
193
+ const proto1 = obj1 ? Object.getPrototypeOf(obj1) : null;
194
+ const proto2 = obj2 ? Object.getPrototypeOf(obj2) : null;
195
+ // Add __proto__ to comparison if prototypes differ
196
+ if (proto1 !== proto2) {
197
+ allKeys.add('__proto__');
198
+ }
199
+ // Check all properties from both objects
200
+ for (const key of allKeys) {
201
+ if (this.isFunction(obj1?.[key]) || this.isFunction(obj2?.[key])) {
202
+ continue;
203
+ }
204
+ let value1;
205
+ let value2;
206
+ // Special handling for __proto__
207
+ if (key === '__proto__') {
208
+ // We already know prototypes differ if we got here (since we added __proto__ to allKeys)
209
+ // For prototypes, we'll do a shallow comparison to avoid infinite recursion
210
+ if (proto1 === proto2) {
211
+ continue; // Should not happen since we only added this key when they differ
212
+ }
213
+ // Compare prototypes - treat default Object.prototype as "undefined" for comparison purposes
214
+ const isDefaultProto1 = proto1 === Object.prototype || proto1 === null;
215
+ const isDefaultProto2 = proto2 === Object.prototype || proto2 === null;
216
+ // If one has default prototype and other has custom, treat as create/delete
217
+ if (isDefaultProto1 && !isDefaultProto2) {
218
+ diff[key] = {
219
+ type: DiffType.CREATED,
220
+ data: proto2
221
+ };
222
+ continue;
223
+ }
224
+ if (!isDefaultProto1 && isDefaultProto2) {
225
+ diff[key] = {
226
+ type: DiffType.DELETED,
227
+ data: proto1
228
+ };
229
+ continue;
230
+ }
231
+ // Both have custom prototypes but they're different
232
+ if (!isDefaultProto1 && !isDefaultProto2 && proto1 !== proto2) {
233
+ diff[key] = {
234
+ type: DiffType.UPDATED,
235
+ data: proto1
236
+ };
237
+ continue;
238
+ }
239
+ }
240
+ else {
241
+ value1 = obj1?.[key];
242
+ value2 = obj2?.[key];
243
+ }
244
+ diff[key] = this._map(value1, value2);
245
+ }
246
+ return diff;
247
+ }
248
+ map(obj1, obj2) {
249
+ // Reset visited pairs for each new comparison
250
+ this.visitedPairs = new WeakMap();
251
+ return this._map(obj1, obj2);
252
+ }
253
+ compare(obj1, obj2) {
254
+ return this.map(obj1, obj2);
255
+ }
256
+ }
257
+ // Export singleton instance
258
+ export const deepDiffMapper = new DeepDiffMapper();
259
+ // Export class for custom instances if needed
260
+ export { DeepDiffMapper };
261
+ // Export convenience function
262
+ export function compareObjects(obj1, obj2) {
263
+ return deepDiffMapper.compare(obj1, obj2);
264
+ }
@@ -0,0 +1 @@
1
+ export * from './deepDiffMapper';
@@ -0,0 +1 @@
1
+ export * from './deepDiffMapper';
@@ -4,6 +4,7 @@ export * from './auth';
4
4
  export * from './browser-storage';
5
5
  export * from './constants';
6
6
  export * from './custom-code';
7
+ export * from './diff-mapper';
7
8
  export * from './helpers';
8
9
  export * from './logger';
9
10
  export * from './webflow-canvas';
@@ -4,6 +4,7 @@ export * from './auth';
4
4
  export * from './browser-storage';
5
5
  export * from './constants';
6
6
  export * from './custom-code';
7
+ export * from './diff-mapper';
7
8
  export * from './helpers';
8
9
  export * from './logger';
9
10
  export * from './webflow-canvas';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finsweet/webflow-apps-utils",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "Shared utilities for Webflow apps",
5
5
  "homepage": "https://github.com/finsweet/webflow-apps-utils",
6
6
  "repository": {