@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.
- package/dist/providers/GlobalProvider.stories.d.ts +5 -0
- package/dist/providers/GlobalProvider.stories.js +419 -0
- package/dist/providers/GlobalProviderDemo.svelte +266 -0
- package/dist/providers/GlobalProviderDemo.svelte.d.ts +3 -0
- package/dist/providers/configuratorUtils.d.ts +11 -14
- package/dist/providers/configuratorUtils.js +68 -115
- package/dist/providers/index.d.ts +1 -1
- package/dist/providers/index.js +1 -1
- package/dist/router/Router.stories.js +519 -2
- package/dist/stores/forms/Form.stories.d.ts +5 -0
- package/dist/stores/forms/Form.stories.js +342 -0
- package/dist/stores/forms/FormDemo.svelte +545 -0
- package/dist/stores/forms/FormDemo.svelte.d.ts +18 -0
- package/dist/ui/components/button/Button.svelte +1 -1
- package/dist/ui/components/copy-text/CopyText.stories.js +1 -1
- package/dist/ui/components/copy-text/CopyText.svelte +17 -19
- package/dist/ui/components/layout/Layout.svelte +38 -5
- package/dist/ui/components/layout/Layout.svelte.d.ts +24 -1
- package/dist/ui/components/layout/examples/ExampleLayout.svelte +12 -12
- package/dist/ui/components/section/Section.svelte +4 -2
- package/dist/ui/index.css +6 -2
- package/dist/utils/diff-mapper/DiffMapper.stories.d.ts +5 -0
- package/dist/utils/diff-mapper/DiffMapper.stories.js +185 -0
- package/dist/utils/diff-mapper/DiffMapperDemo.svelte +351 -0
- package/dist/utils/diff-mapper/DiffMapperDemo.svelte.d.ts +18 -0
- package/dist/utils/diff-mapper/deepDiffMapper.d.ts +31 -0
- package/dist/utils/diff-mapper/deepDiffMapper.js +264 -0
- package/dist/utils/diff-mapper/index.d.ts +1 -0
- package/dist/utils/diff-mapper/index.js +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/package.json +1 -1
- package/dist/providers/GlobalProvider.mdx +0 -322
- package/dist/router/Router.mdx +0 -958
- 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';
|
package/dist/utils/index.d.ts
CHANGED
package/dist/utils/index.js
CHANGED