@memberjunction/ng-deep-diff 5.40.1 → 5.41.0
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/lib/__tests__/deep-diff.component.test.d.ts +2 -0
- package/dist/lib/__tests__/deep-diff.component.test.d.ts.map +1 -0
- package/dist/lib/__tests__/deep-diff.component.test.js +178 -0
- package/dist/lib/__tests__/deep-diff.component.test.js.map +1 -0
- package/dist/lib/deep-diff.component.d.ts +31 -3
- package/dist/lib/deep-diff.component.d.ts.map +1 -1
- package/dist/lib/deep-diff.component.js +68 -5
- package/dist/lib/deep-diff.component.js.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deep-diff.component.test.d.ts","sourceRoot":"","sources":["../../../src/lib/__tests__/deep-diff.component.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
+
/**
|
|
3
|
+
* Recompute-trigger coverage for the DeepDiffComponent precomputes (PR #2841):
|
|
4
|
+
*
|
|
5
|
+
* - `filteredItems`: a precomputed array (was a getter that re-walked the tree
|
|
6
|
+
* every CD pass). Must recompute on the `filter` / `filterType` setters AND on
|
|
7
|
+
* `generateDiff()` (data change via oldValue/newValue setters), and must match
|
|
8
|
+
* the naive filter the old getter produced.
|
|
9
|
+
*
|
|
10
|
+
* - `formatValue` / `isValueTruncated`: memoized by `${path}|${expanded}`. The
|
|
11
|
+
* cache must (a) hit on repeat calls, (b) be keyed by expansion state so an
|
|
12
|
+
* expand/collapse yields the correct (truncated vs full) value, and (c) be
|
|
13
|
+
* INVALIDATED when the source data regenerates — a stale cached value must not
|
|
14
|
+
* leak after new data lands.
|
|
15
|
+
*
|
|
16
|
+
* Uses the REAL DeepDiffer (`@memberjunction/global`); only Angular's decorator
|
|
17
|
+
* surface is stubbed so the component can be `new`'d in a node unit test.
|
|
18
|
+
*/
|
|
19
|
+
vi.mock('@angular/core', () => ({
|
|
20
|
+
Component: () => (t) => t,
|
|
21
|
+
Input: () => () => { },
|
|
22
|
+
Output: () => () => { },
|
|
23
|
+
EventEmitter: class {
|
|
24
|
+
emit() { }
|
|
25
|
+
},
|
|
26
|
+
ChangeDetectorRef: class {
|
|
27
|
+
detectChanges() { }
|
|
28
|
+
markForCheck() { }
|
|
29
|
+
},
|
|
30
|
+
ChangeDetectionStrategy: { OnPush: 1 },
|
|
31
|
+
OnInit: class {
|
|
32
|
+
},
|
|
33
|
+
}));
|
|
34
|
+
import { DeepDiffComponent } from '../deep-diff.component';
|
|
35
|
+
function makeComponent() {
|
|
36
|
+
const cdr = { detectChanges: vi.fn(), markForCheck: vi.fn() };
|
|
37
|
+
return new DeepDiffComponent(cdr);
|
|
38
|
+
}
|
|
39
|
+
/** Flatten the filtered tree to its paths (order-preserving) for stable assertions. */
|
|
40
|
+
function paths(items) {
|
|
41
|
+
const out = [];
|
|
42
|
+
const walk = (list) => {
|
|
43
|
+
for (const i of list) {
|
|
44
|
+
out.push(i.path);
|
|
45
|
+
walk(i.children);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
walk(items);
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
describe('DeepDiffComponent — filteredItems recompute', () => {
|
|
52
|
+
let component;
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
component = makeComponent();
|
|
55
|
+
// A representative object diff with several change types.
|
|
56
|
+
component.oldValue = { name: 'alpha', count: 1, removed: 'gone', nested: { keep: 1 } };
|
|
57
|
+
component.newValue = { name: 'beta', count: 1, added: 'fresh', nested: { keep: 1 } };
|
|
58
|
+
component.ngOnInit();
|
|
59
|
+
});
|
|
60
|
+
it('builds filteredItems on data change (generateDiff) — non-empty for a real diff', () => {
|
|
61
|
+
expect(component.diffItems.length).toBeGreaterThan(0);
|
|
62
|
+
// filter='' + type='all' → filteredItems is exactly diffItems (identity fast-path)
|
|
63
|
+
expect(component.filteredItems).toBe(component.diffItems);
|
|
64
|
+
});
|
|
65
|
+
it('recomputes filteredItems when the filterType setter changes', () => {
|
|
66
|
+
const before = component.filteredItems;
|
|
67
|
+
component.filterType = 'added';
|
|
68
|
+
const after = component.filteredItems;
|
|
69
|
+
expect(after).not.toBe(before);
|
|
70
|
+
// every surviving leaf (no children kept) should be an "added" path, and 'added' must appear
|
|
71
|
+
const ps = paths(after);
|
|
72
|
+
expect(ps.some((p) => p.includes('added'))).toBe(true);
|
|
73
|
+
expect(ps.some((p) => p.includes('removed'))).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
it('recomputes filteredItems when the text filter setter changes', () => {
|
|
76
|
+
component.filter = 'name';
|
|
77
|
+
const ps = paths(component.filteredItems);
|
|
78
|
+
expect(ps).toContain('name');
|
|
79
|
+
expect(ps).not.toContain('removed');
|
|
80
|
+
});
|
|
81
|
+
it('text filter is case-insensitive on path', () => {
|
|
82
|
+
component.filter = 'NAME';
|
|
83
|
+
expect(paths(component.filteredItems)).toContain('name');
|
|
84
|
+
});
|
|
85
|
+
it('reverts to the identity fast-path when filters are cleared', () => {
|
|
86
|
+
component.filter = 'name';
|
|
87
|
+
expect(component.filteredItems).not.toBe(component.diffItems);
|
|
88
|
+
component.filter = '';
|
|
89
|
+
component.filterType = 'all';
|
|
90
|
+
expect(component.filteredItems).toBe(component.diffItems);
|
|
91
|
+
});
|
|
92
|
+
it('matches the old getter baseline (naive recursive filter) under an active filter', () => {
|
|
93
|
+
component.filterType = 'modified';
|
|
94
|
+
// Baseline: keep an item if it is modified OR any descendant survives.
|
|
95
|
+
const baseline = (items) => items.reduce((acc, item) => {
|
|
96
|
+
const kids = baseline(item.children);
|
|
97
|
+
const isModified = String(item.type) === 'modified';
|
|
98
|
+
if (isModified || kids.length > 0)
|
|
99
|
+
acc.push({ ...item, children: kids });
|
|
100
|
+
return acc;
|
|
101
|
+
}, []);
|
|
102
|
+
expect(paths(component.filteredItems)).toEqual(paths(baseline(component.diffItems)));
|
|
103
|
+
});
|
|
104
|
+
it('re-derives filteredItems when new source data is set (regen trigger)', () => {
|
|
105
|
+
component.filterType = 'added';
|
|
106
|
+
const firstAddedPaths = paths(component.filteredItems);
|
|
107
|
+
// brand-new data with a DIFFERENT added key
|
|
108
|
+
component.oldValue = { x: 1 };
|
|
109
|
+
component.newValue = { x: 1, brandNew: 'v' };
|
|
110
|
+
// generateDiff ran via the setter; the active 'added' filter re-applied to NEW data
|
|
111
|
+
const secondAddedPaths = paths(component.filteredItems);
|
|
112
|
+
expect(secondAddedPaths).not.toEqual(firstAddedPaths);
|
|
113
|
+
expect(secondAddedPaths.some((p) => p.includes('brandNew'))).toBe(true);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
describe('DeepDiffComponent — formatValue / isValueTruncated memoization', () => {
|
|
117
|
+
let component;
|
|
118
|
+
beforeEach(() => {
|
|
119
|
+
component = makeComponent();
|
|
120
|
+
component.maxStringLength = 10;
|
|
121
|
+
component.truncateValues = true;
|
|
122
|
+
});
|
|
123
|
+
it('returns the same cached string on repeat formatValue calls for a (path, expanded) key', () => {
|
|
124
|
+
const longString = 'x'.repeat(50);
|
|
125
|
+
const first = component.formatValue(longString, 'a.b');
|
|
126
|
+
const second = component.formatValue(longString, 'a.b');
|
|
127
|
+
expect(first).toBe(second);
|
|
128
|
+
// truncated form (maxStringLength=10) ends with the ellipsis marker
|
|
129
|
+
expect(first.endsWith('..."')).toBe(true);
|
|
130
|
+
});
|
|
131
|
+
it('keys the cache by expansion state — expanded path yields the FULL value, collapsed the truncated one', () => {
|
|
132
|
+
const longString = 'y'.repeat(50);
|
|
133
|
+
// collapsed → truncated
|
|
134
|
+
const collapsed = component.formatValue(longString, 'p');
|
|
135
|
+
expect(collapsed.endsWith('..."')).toBe(true);
|
|
136
|
+
// expand that path, then re-format: must NOT serve the collapsed cache entry
|
|
137
|
+
component.toggleValueExpansion('p', { stopPropagation: vi.fn() });
|
|
138
|
+
const expanded = component.formatValue(longString, 'p');
|
|
139
|
+
expect(expanded).toBe(`"${longString}"`);
|
|
140
|
+
expect(expanded).not.toBe(collapsed);
|
|
141
|
+
});
|
|
142
|
+
it('isValueTruncated reports truncation for long strings and caches the result', () => {
|
|
143
|
+
const longString = 'z'.repeat(50);
|
|
144
|
+
expect(component.isValueTruncated(longString, 'q')).toBe(true);
|
|
145
|
+
// short string under the cap is not truncated
|
|
146
|
+
expect(component.isValueTruncated('short', 'r')).toBe(false);
|
|
147
|
+
});
|
|
148
|
+
it('isValueTruncated returns false once the path is expanded', () => {
|
|
149
|
+
const longString = 'w'.repeat(50);
|
|
150
|
+
expect(component.isValueTruncated(longString, 's')).toBe(true);
|
|
151
|
+
component.toggleValueExpansion('s', { stopPropagation: vi.fn() });
|
|
152
|
+
expect(component.isValueTruncated(longString, 's')).toBe(false);
|
|
153
|
+
});
|
|
154
|
+
it('does NOT leak a stale cached value after the source data regenerates', () => {
|
|
155
|
+
// Prime the cache for path 'root' under the first dataset.
|
|
156
|
+
component.oldValue = { root: 'a'.repeat(50) };
|
|
157
|
+
component.newValue = { root: 'a'.repeat(50) };
|
|
158
|
+
component.ngOnInit();
|
|
159
|
+
const firstFormatted = component.formatValue('a'.repeat(50), 'root');
|
|
160
|
+
expect(firstFormatted.endsWith('..."')).toBe(true);
|
|
161
|
+
// Regenerate with a DIFFERENT value at the same path. generateDiff clears the
|
|
162
|
+
// memo caches, so formatting 'root' again must reflect the NEW value, not the
|
|
163
|
+
// stale cached string.
|
|
164
|
+
const newVal = 'b'.repeat(50);
|
|
165
|
+
component.oldValue = { root: newVal };
|
|
166
|
+
component.newValue = { root: 'changed-' + newVal };
|
|
167
|
+
const afterRegen = component.formatValue('changed-' + newVal, 'root');
|
|
168
|
+
expect(afterRegen).toContain('changed-');
|
|
169
|
+
expect(afterRegen).not.toBe(firstFormatted);
|
|
170
|
+
});
|
|
171
|
+
it('caches object formatting (computeFormattedValue runs once per (path, expanded))', () => {
|
|
172
|
+
const obj = { a: 1, b: 2, c: 3, d: 'x'.repeat(300) };
|
|
173
|
+
const first = component.formatValue(obj, 'obj');
|
|
174
|
+
const second = component.formatValue(obj, 'obj');
|
|
175
|
+
expect(second).toBe(first);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
//# sourceMappingURL=deep-diff.component.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deep-diff.component.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/deep-diff.component.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it, expect, beforeEach, vi } from 'vitest';\n\n/**\n * Recompute-trigger coverage for the DeepDiffComponent precomputes (PR #2841):\n *\n * - `filteredItems`: a precomputed array (was a getter that re-walked the tree\n * every CD pass). Must recompute on the `filter` / `filterType` setters AND on\n * `generateDiff()` (data change via oldValue/newValue setters), and must match\n * the naive filter the old getter produced.\n *\n * - `formatValue` / `isValueTruncated`: memoized by `${path}|${expanded}`. The\n * cache must (a) hit on repeat calls, (b) be keyed by expansion state so an\n * expand/collapse yields the correct (truncated vs full) value, and (c) be\n * INVALIDATED when the source data regenerates — a stale cached value must not\n * leak after new data lands.\n *\n * Uses the REAL DeepDiffer (`@memberjunction/global`); only Angular's decorator\n * surface is stubbed so the component can be `new`'d in a node unit test.\n */\n\nvi.mock('@angular/core', () => ({\n Component: () => (t: Function) => t,\n Input: () => () => {},\n Output: () => () => {},\n EventEmitter: class { emit() {} },\n ChangeDetectorRef: class { detectChanges() {} markForCheck() {} },\n ChangeDetectionStrategy: { OnPush: 1 },\n OnInit: class {},\n}));\n\nimport { DeepDiffComponent, DeepDiffItem } from '../deep-diff.component';\nimport { ChangeDetectorRef } from '@angular/core';\n\nfunction makeComponent(): DeepDiffComponent {\n const cdr = { detectChanges: vi.fn(), markForCheck: vi.fn() } as unknown as ChangeDetectorRef;\n return new DeepDiffComponent(cdr);\n}\n\n/** Flatten the filtered tree to its paths (order-preserving) for stable assertions. */\nfunction paths(items: DeepDiffItem[]): string[] {\n const out: string[] = [];\n const walk = (list: DeepDiffItem[]) => {\n for (const i of list) {\n out.push(i.path);\n walk(i.children);\n }\n };\n walk(items);\n return out;\n}\n\ndescribe('DeepDiffComponent — filteredItems recompute', () => {\n let component: DeepDiffComponent;\n\n beforeEach(() => {\n component = makeComponent();\n // A representative object diff with several change types.\n component.oldValue = { name: 'alpha', count: 1, removed: 'gone', nested: { keep: 1 } };\n component.newValue = { name: 'beta', count: 1, added: 'fresh', nested: { keep: 1 } };\n component.ngOnInit();\n });\n\n it('builds filteredItems on data change (generateDiff) — non-empty for a real diff', () => {\n expect(component.diffItems.length).toBeGreaterThan(0);\n // filter='' + type='all' → filteredItems is exactly diffItems (identity fast-path)\n expect(component.filteredItems).toBe(component.diffItems);\n });\n\n it('recomputes filteredItems when the filterType setter changes', () => {\n const before = component.filteredItems;\n component.filterType = 'added';\n const after = component.filteredItems;\n expect(after).not.toBe(before);\n // every surviving leaf (no children kept) should be an \"added\" path, and 'added' must appear\n const ps = paths(after);\n expect(ps.some((p) => p.includes('added'))).toBe(true);\n expect(ps.some((p) => p.includes('removed'))).toBe(false);\n });\n\n it('recomputes filteredItems when the text filter setter changes', () => {\n component.filter = 'name';\n const ps = paths(component.filteredItems);\n expect(ps).toContain('name');\n expect(ps).not.toContain('removed');\n });\n\n it('text filter is case-insensitive on path', () => {\n component.filter = 'NAME';\n expect(paths(component.filteredItems)).toContain('name');\n });\n\n it('reverts to the identity fast-path when filters are cleared', () => {\n component.filter = 'name';\n expect(component.filteredItems).not.toBe(component.diffItems);\n component.filter = '';\n component.filterType = 'all';\n expect(component.filteredItems).toBe(component.diffItems);\n });\n\n it('matches the old getter baseline (naive recursive filter) under an active filter', () => {\n component.filterType = 'modified';\n // Baseline: keep an item if it is modified OR any descendant survives.\n const baseline = (items: DeepDiffItem[]): DeepDiffItem[] =>\n items.reduce<DeepDiffItem[]>((acc, item) => {\n const kids = baseline(item.children);\n const isModified = String(item.type) === 'modified';\n if (isModified || kids.length > 0) acc.push({ ...item, children: kids });\n return acc;\n }, []);\n expect(paths(component.filteredItems)).toEqual(paths(baseline(component.diffItems)));\n });\n\n it('re-derives filteredItems when new source data is set (regen trigger)', () => {\n component.filterType = 'added';\n const firstAddedPaths = paths(component.filteredItems);\n\n // brand-new data with a DIFFERENT added key\n component.oldValue = { x: 1 };\n component.newValue = { x: 1, brandNew: 'v' };\n // generateDiff ran via the setter; the active 'added' filter re-applied to NEW data\n const secondAddedPaths = paths(component.filteredItems);\n expect(secondAddedPaths).not.toEqual(firstAddedPaths);\n expect(secondAddedPaths.some((p) => p.includes('brandNew'))).toBe(true);\n });\n});\n\ndescribe('DeepDiffComponent — formatValue / isValueTruncated memoization', () => {\n let component: DeepDiffComponent;\n\n beforeEach(() => {\n component = makeComponent();\n component.maxStringLength = 10;\n component.truncateValues = true;\n });\n\n it('returns the same cached string on repeat formatValue calls for a (path, expanded) key', () => {\n const longString = 'x'.repeat(50);\n const first = component.formatValue(longString, 'a.b');\n const second = component.formatValue(longString, 'a.b');\n expect(first).toBe(second);\n // truncated form (maxStringLength=10) ends with the ellipsis marker\n expect(first.endsWith('...\"')).toBe(true);\n });\n\n it('keys the cache by expansion state — expanded path yields the FULL value, collapsed the truncated one', () => {\n const longString = 'y'.repeat(50);\n // collapsed → truncated\n const collapsed = component.formatValue(longString, 'p');\n expect(collapsed.endsWith('...\"')).toBe(true);\n\n // expand that path, then re-format: must NOT serve the collapsed cache entry\n component.toggleValueExpansion('p', { stopPropagation: vi.fn() } as unknown as Event);\n const expanded = component.formatValue(longString, 'p');\n expect(expanded).toBe(`\"${longString}\"`);\n expect(expanded).not.toBe(collapsed);\n });\n\n it('isValueTruncated reports truncation for long strings and caches the result', () => {\n const longString = 'z'.repeat(50);\n expect(component.isValueTruncated(longString, 'q')).toBe(true);\n // short string under the cap is not truncated\n expect(component.isValueTruncated('short', 'r')).toBe(false);\n });\n\n it('isValueTruncated returns false once the path is expanded', () => {\n const longString = 'w'.repeat(50);\n expect(component.isValueTruncated(longString, 's')).toBe(true);\n component.toggleValueExpansion('s', { stopPropagation: vi.fn() } as unknown as Event);\n expect(component.isValueTruncated(longString, 's')).toBe(false);\n });\n\n it('does NOT leak a stale cached value after the source data regenerates', () => {\n // Prime the cache for path 'root' under the first dataset.\n component.oldValue = { root: 'a'.repeat(50) };\n component.newValue = { root: 'a'.repeat(50) };\n component.ngOnInit();\n const firstFormatted = component.formatValue('a'.repeat(50), 'root');\n expect(firstFormatted.endsWith('...\"')).toBe(true);\n\n // Regenerate with a DIFFERENT value at the same path. generateDiff clears the\n // memo caches, so formatting 'root' again must reflect the NEW value, not the\n // stale cached string.\n const newVal = 'b'.repeat(50);\n component.oldValue = { root: newVal };\n component.newValue = { root: 'changed-' + newVal };\n const afterRegen = component.formatValue('changed-' + newVal, 'root');\n expect(afterRegen).toContain('changed-');\n expect(afterRegen).not.toBe(firstFormatted);\n });\n\n it('caches object formatting (computeFormattedValue runs once per (path, expanded))', () => {\n const obj = { a: 1, b: 2, c: 3, d: 'x'.repeat(300) };\n const first = component.formatValue(obj, 'obj');\n const second = component.formatValue(obj, 'obj');\n expect(second).toBe(first);\n });\n});\n"]}
|
|
@@ -33,11 +33,32 @@ export declare class DeepDiffComponent implements OnInit {
|
|
|
33
33
|
truncateValues: boolean;
|
|
34
34
|
diffResult: DeepDiffResult | null;
|
|
35
35
|
diffItems: DeepDiffItem[];
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
private _filter;
|
|
37
|
+
get filter(): string;
|
|
38
|
+
set filter(value: string);
|
|
39
|
+
private _filterType;
|
|
40
|
+
get filterType(): 'all' | 'added' | 'removed' | 'modified' | 'unchanged';
|
|
41
|
+
set filterType(value: 'all' | 'added' | 'removed' | 'modified' | 'unchanged');
|
|
42
|
+
/**
|
|
43
|
+
* Precomputed filtered diff tree. Recomputed only when the source data
|
|
44
|
+
* (`diffItems`) or the filter inputs (`filter` / `filterType`) change —
|
|
45
|
+
* never on every change-detection cycle. Bound directly by the template's
|
|
46
|
+
* `@for` and empty-state length check, which preserves `@for` referential
|
|
47
|
+
* stability between CD passes (the recursive walk no longer runs >= 2x/CD).
|
|
48
|
+
*/
|
|
49
|
+
filteredItems: DeepDiffItem[];
|
|
38
50
|
expandedValuesMap: {
|
|
39
51
|
[key: string]: boolean;
|
|
40
52
|
};
|
|
53
|
+
/**
|
|
54
|
+
* Memoization cache for {@link formatValue} / {@link isValueTruncated}. Keyed
|
|
55
|
+
* by `${path}|${expanded}` so the expensive `JSON.stringify` of object values
|
|
56
|
+
* runs at most once per (path, expansion-state) instead of on every
|
|
57
|
+
* change-detection cycle (the template binds these in a recursive `@for`).
|
|
58
|
+
* Invalidated whenever the source data is regenerated.
|
|
59
|
+
*/
|
|
60
|
+
private formatValueCache;
|
|
61
|
+
private truncatedCache;
|
|
41
62
|
private differ;
|
|
42
63
|
constructor(cdr: ChangeDetectorRef);
|
|
43
64
|
ngOnInit(): void;
|
|
@@ -47,11 +68,18 @@ export declare class DeepDiffComponent implements OnInit {
|
|
|
47
68
|
toggleItem(item: DeepDiffItem): void;
|
|
48
69
|
expandAllItems(): void;
|
|
49
70
|
collapseAllItems(): void;
|
|
50
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Recomputes {@link filteredItems} from the current `diffItems` + filter
|
|
73
|
+
* inputs. Called from `generateDiff()` (data change) and from the
|
|
74
|
+
* `filter` / `filterType` setters (filter change) — NOT per CD cycle.
|
|
75
|
+
*/
|
|
76
|
+
private recomputeFilteredItems;
|
|
51
77
|
getIcon(type: DiffChangeType): string;
|
|
52
78
|
getTypeClass(type: DiffChangeType): string;
|
|
53
79
|
formatValue(value: any, path: string): string;
|
|
80
|
+
private computeFormattedValue;
|
|
54
81
|
isValueTruncated(value: any, path: string): boolean;
|
|
82
|
+
private computeValueTruncated;
|
|
55
83
|
toggleValueExpansion(path: string, event: Event): void;
|
|
56
84
|
private getObjectSize;
|
|
57
85
|
copyToClipboard(text: string): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deep-diff.component.d.ts","sourceRoot":"","sources":["../../src/lib/deep-diff.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,MAAM,EAAE,iBAAiB,EAA2B,MAAM,eAAe,CAAC;AACrG,OAAO,EAAc,cAAc,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;;AAEhG,MAAM,WAAW,YAAa,SAAQ,UAAU;IAC9C,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,qBAOa,iBAAkB,YAAW,MAAM;
|
|
1
|
+
{"version":3,"file":"deep-diff.component.d.ts","sourceRoot":"","sources":["../../src/lib/deep-diff.component.ts"],"names":[],"mappings":"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"}
|
|
@@ -358,9 +358,40 @@ export class DeepDiffComponent {
|
|
|
358
358
|
truncateValues = true;
|
|
359
359
|
diffResult = null;
|
|
360
360
|
diffItems = [];
|
|
361
|
-
|
|
362
|
-
|
|
361
|
+
_filter = '';
|
|
362
|
+
get filter() {
|
|
363
|
+
return this._filter;
|
|
364
|
+
}
|
|
365
|
+
set filter(value) {
|
|
366
|
+
this._filter = value;
|
|
367
|
+
this.recomputeFilteredItems();
|
|
368
|
+
}
|
|
369
|
+
_filterType = 'all';
|
|
370
|
+
get filterType() {
|
|
371
|
+
return this._filterType;
|
|
372
|
+
}
|
|
373
|
+
set filterType(value) {
|
|
374
|
+
this._filterType = value;
|
|
375
|
+
this.recomputeFilteredItems();
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Precomputed filtered diff tree. Recomputed only when the source data
|
|
379
|
+
* (`diffItems`) or the filter inputs (`filter` / `filterType`) change —
|
|
380
|
+
* never on every change-detection cycle. Bound directly by the template's
|
|
381
|
+
* `@for` and empty-state length check, which preserves `@for` referential
|
|
382
|
+
* stability between CD passes (the recursive walk no longer runs >= 2x/CD).
|
|
383
|
+
*/
|
|
384
|
+
filteredItems = [];
|
|
363
385
|
expandedValuesMap = {};
|
|
386
|
+
/**
|
|
387
|
+
* Memoization cache for {@link formatValue} / {@link isValueTruncated}. Keyed
|
|
388
|
+
* by `${path}|${expanded}` so the expensive `JSON.stringify` of object values
|
|
389
|
+
* runs at most once per (path, expansion-state) instead of on every
|
|
390
|
+
* change-detection cycle (the template binds these in a recursive `@for`).
|
|
391
|
+
* Invalidated whenever the source data is regenerated.
|
|
392
|
+
*/
|
|
393
|
+
formatValueCache = new Map();
|
|
394
|
+
truncatedCache = new Map();
|
|
364
395
|
differ;
|
|
365
396
|
constructor(cdr) {
|
|
366
397
|
this.cdr = cdr;
|
|
@@ -384,15 +415,21 @@ export class DeepDiffComponent {
|
|
|
384
415
|
}
|
|
385
416
|
generateDiff() {
|
|
386
417
|
if (this.oldValue === undefined && this.newValue === undefined) {
|
|
418
|
+
this.formatValueCache.clear();
|
|
419
|
+
this.truncatedCache.clear();
|
|
387
420
|
this.diffResult = null;
|
|
388
421
|
this.diffItems = [];
|
|
422
|
+
this.recomputeFilteredItems();
|
|
389
423
|
return;
|
|
390
424
|
}
|
|
425
|
+
this.formatValueCache.clear();
|
|
426
|
+
this.truncatedCache.clear();
|
|
391
427
|
this.diffResult = this.differ.diff(this.oldValue, this.newValue);
|
|
392
428
|
this.diffItems = this.buildHierarchicalItems(this.diffResult.changes);
|
|
393
429
|
if (this.expandAll) {
|
|
394
430
|
this.expandAllItems();
|
|
395
431
|
}
|
|
432
|
+
this.recomputeFilteredItems();
|
|
396
433
|
}
|
|
397
434
|
buildHierarchicalItems(changes) {
|
|
398
435
|
const rootItems = [];
|
|
@@ -454,9 +491,16 @@ export class DeepDiffComponent {
|
|
|
454
491
|
collapse(this.diffItems);
|
|
455
492
|
this.cdr.markForCheck();
|
|
456
493
|
}
|
|
457
|
-
|
|
494
|
+
/**
|
|
495
|
+
* Recomputes {@link filteredItems} from the current `diffItems` + filter
|
|
496
|
+
* inputs. Called from `generateDiff()` (data change) and from the
|
|
497
|
+
* `filter` / `filterType` setters (filter change) — NOT per CD cycle.
|
|
498
|
+
*/
|
|
499
|
+
recomputeFilteredItems() {
|
|
458
500
|
if (!this.filter && this.filterType === 'all') {
|
|
459
|
-
|
|
501
|
+
this.filteredItems = this.diffItems;
|
|
502
|
+
this.cdr.markForCheck();
|
|
503
|
+
return;
|
|
460
504
|
}
|
|
461
505
|
const filterFn = (item) => {
|
|
462
506
|
const matchesType = this.filterType === 'all' ||
|
|
@@ -482,7 +526,8 @@ export class DeepDiffComponent {
|
|
|
482
526
|
return acc;
|
|
483
527
|
}, []);
|
|
484
528
|
};
|
|
485
|
-
|
|
529
|
+
this.filteredItems = filterRecursive(this.diffItems);
|
|
530
|
+
this.cdr.markForCheck();
|
|
486
531
|
}
|
|
487
532
|
getIcon(type) {
|
|
488
533
|
switch (type) {
|
|
@@ -508,6 +553,15 @@ export class DeepDiffComponent {
|
|
|
508
553
|
if (value === null)
|
|
509
554
|
return 'null';
|
|
510
555
|
const isExpanded = !!this.expandedValuesMap[path];
|
|
556
|
+
const cacheKey = `${path}|${isExpanded ? 1 : 0}`;
|
|
557
|
+
const cached = this.formatValueCache.get(cacheKey);
|
|
558
|
+
if (cached !== undefined)
|
|
559
|
+
return cached;
|
|
560
|
+
const formatted = this.computeFormattedValue(value, isExpanded);
|
|
561
|
+
this.formatValueCache.set(cacheKey, formatted);
|
|
562
|
+
return formatted;
|
|
563
|
+
}
|
|
564
|
+
computeFormattedValue(value, isExpanded) {
|
|
511
565
|
const shouldTruncate = this.truncateValues && !isExpanded;
|
|
512
566
|
if (typeof value === 'string') {
|
|
513
567
|
if (shouldTruncate && value.length > this.maxStringLength) {
|
|
@@ -535,6 +589,15 @@ export class DeepDiffComponent {
|
|
|
535
589
|
if (!this.truncateValues || this.expandedValuesMap[path]) {
|
|
536
590
|
return false;
|
|
537
591
|
}
|
|
592
|
+
const cacheKey = `${path}|0`;
|
|
593
|
+
const cached = this.truncatedCache.get(cacheKey);
|
|
594
|
+
if (cached !== undefined)
|
|
595
|
+
return cached;
|
|
596
|
+
const result = this.computeValueTruncated(value);
|
|
597
|
+
this.truncatedCache.set(cacheKey, result);
|
|
598
|
+
return result;
|
|
599
|
+
}
|
|
600
|
+
computeValueTruncated(value) {
|
|
538
601
|
if (typeof value === 'string') {
|
|
539
602
|
return value.length > this.maxStringLength;
|
|
540
603
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deep-diff.component.js","sourceRoot":"","sources":["../../src/lib/deep-diff.component.ts","../../src/lib/deep-diff.component.html"],"names":[],"mappings":"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","sourcesContent":["import { Component, Input, OnInit, ChangeDetectorRef, ChangeDetectionStrategy } from '@angular/core';\nimport { DeepDiffer, DeepDiffResult, DiffChangeType, DiffChange } from '@memberjunction/global';\n\nexport interface DeepDiffItem extends DiffChange {\n level: number;\n isExpanded: boolean;\n children: DeepDiffItem[];\n parentPath: string;\n}\n\n@Component({\n standalone: false,\n selector: 'mj-deep-diff',\n templateUrl: './deep-diff.component.html',\n styleUrls: ['./deep-diff.component.css'],\n changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class DeepDiffComponent implements OnInit {\n private _oldValue: any;\n private _newValue: any;\n private _showUnchanged: boolean = false;\n private _maxDepth: number = 10;\n private _maxStringLength: number = 100;\n private _treatNullAsUndefined: boolean = false;\n\n @Input()\n get oldValue(): any {\n return this._oldValue;\n }\n set oldValue(value: any) {\n this._oldValue = value;\n this.generateDiff();\n }\n\n @Input()\n get newValue(): any {\n return this._newValue;\n }\n set newValue(value: any) {\n this._newValue = value;\n this.generateDiff();\n }\n\n @Input()\n get showUnchanged(): boolean {\n return this._showUnchanged;\n }\n set showUnchanged(value: boolean) {\n this._showUnchanged = value;\n this.updateDifferConfig();\n this.generateDiff();\n }\n\n @Input()\n get maxDepth(): number {\n return this._maxDepth;\n }\n set maxDepth(value: number) {\n this._maxDepth = value;\n this.updateDifferConfig();\n this.generateDiff();\n }\n\n @Input()\n get maxStringLength(): number {\n return this._maxStringLength;\n }\n set maxStringLength(value: number) {\n this._maxStringLength = value;\n this.updateDifferConfig();\n this.generateDiff();\n }\n\n @Input()\n get treatNullAsUndefined(): boolean {\n return this._treatNullAsUndefined;\n }\n set treatNullAsUndefined(value: boolean) {\n this._treatNullAsUndefined = value;\n this.updateDifferConfig();\n this.generateDiff();\n }\n\n @Input() title: string = 'Deep Diff Analysis';\n @Input() showSummary: boolean = true;\n @Input() expandAll: boolean = false;\n @Input() truncateValues: boolean = true;\n\n diffResult: DeepDiffResult | null = null;\n diffItems: DeepDiffItem[] = [];\n filter: string = '';\n filterType: 'all' | 'added' | 'removed' | 'modified' | 'unchanged' = 'all';\n expandedValuesMap: { [key: string]: boolean } = {};\n \n private differ: DeepDiffer;\n\n constructor(private cdr: ChangeDetectorRef) {\n this.differ = new DeepDiffer({\n includeUnchanged: false,\n maxDepth: this.maxDepth,\n maxStringLength: this.maxStringLength,\n treatNullAsUndefined: this.treatNullAsUndefined\n });\n }\n\n ngOnInit(): void {\n this.generateDiff();\n }\n\n\n private updateDifferConfig(): void {\n this.differ.updateConfig({\n includeUnchanged: this.showUnchanged,\n maxDepth: this.maxDepth,\n maxStringLength: this.maxStringLength,\n treatNullAsUndefined: this.treatNullAsUndefined\n });\n }\n\n private generateDiff(): void {\n if (this.oldValue === undefined && this.newValue === undefined) {\n this.diffResult = null;\n this.diffItems = [];\n return;\n }\n\n this.diffResult = this.differ.diff(this.oldValue, this.newValue);\n this.diffItems = this.buildHierarchicalItems(this.diffResult.changes);\n \n if (this.expandAll) {\n this.expandAllItems();\n }\n }\n\n private buildHierarchicalItems(changes: DiffChange[]): DeepDiffItem[] {\n const rootItems: DeepDiffItem[] = [];\n const itemMap = new Map<string, DeepDiffItem>();\n\n // Sort by path to ensure parents come before children\n const sortedChanges = [...changes].sort((a, b) => {\n const aDepth = a.path.split('.').length;\n const bDepth = b.path.split('.').length;\n if (aDepth !== bDepth) return aDepth - bDepth;\n return a.path.localeCompare(b.path);\n });\n\n for (const change of sortedChanges) {\n const pathParts = change.path === 'root' ? [] : change.path.split('.');\n const level = pathParts.length;\n const parentPath = pathParts.slice(0, -1).join('.');\n \n const item: DeepDiffItem = {\n ...change,\n level,\n isExpanded: level === 0, // Expand only root level items by default\n children: [],\n parentPath\n };\n\n itemMap.set(change.path, item);\n\n if (parentPath && itemMap.has(parentPath)) {\n itemMap.get(parentPath)!.children.push(item);\n } else {\n rootItems.push(item);\n }\n }\n\n return rootItems;\n }\n\n toggleItem(item: DeepDiffItem): void {\n item.isExpanded = !item.isExpanded;\n this.cdr.markForCheck();\n }\n\n expandAllItems(): void {\n const expand = (items: DeepDiffItem[]) => {\n for (const item of items) {\n item.isExpanded = true;\n if (item.children.length > 0) {\n expand(item.children);\n }\n }\n };\n expand(this.diffItems);\n this.cdr.markForCheck();\n }\n\n collapseAllItems(): void {\n const collapse = (items: DeepDiffItem[]) => {\n for (const item of items) {\n item.isExpanded = false;\n if (item.children.length > 0) {\n collapse(item.children);\n }\n }\n };\n collapse(this.diffItems);\n this.cdr.markForCheck();\n }\n\n get filteredItems(): DeepDiffItem[] {\n if (!this.filter && this.filterType === 'all') {\n return this.diffItems;\n }\n\n const filterFn = (item: DeepDiffItem): boolean => {\n const matchesType = this.filterType === 'all' || \n (this.filterType === 'added' && item.type === DiffChangeType.Added) ||\n (this.filterType === 'removed' && item.type === DiffChangeType.Removed) ||\n (this.filterType === 'modified' && item.type === DiffChangeType.Modified) ||\n (this.filterType === 'unchanged' && item.type === DiffChangeType.Unchanged);\n const matchesText = !this.filter || \n item.path.toLowerCase().includes(this.filter.toLowerCase()) ||\n item.description.toLowerCase().includes(this.filter.toLowerCase());\n \n return matchesType && matchesText;\n };\n\n const filterRecursive = (items: DeepDiffItem[]): DeepDiffItem[] => {\n return items.reduce((acc, item) => {\n const childMatches = filterRecursive(item.children);\n if (filterFn(item) || childMatches.length > 0) {\n acc.push({\n ...item,\n children: childMatches,\n isExpanded: item.isExpanded // Preserve the original expanded state\n });\n }\n return acc;\n }, [] as DeepDiffItem[]);\n };\n\n return filterRecursive(this.diffItems);\n }\n\n getIcon(type: DiffChangeType): string {\n switch (type) {\n case DiffChangeType.Added: return 'fa-plus-circle';\n case DiffChangeType.Removed: return 'fa-minus-circle';\n case DiffChangeType.Modified: return 'fa-edit';\n case DiffChangeType.Unchanged: return 'fa-check-circle';\n default: return 'fa-question-circle';\n }\n }\n\n getTypeClass(type: DiffChangeType): string {\n switch (type) {\n case DiffChangeType.Added: return 'added';\n case DiffChangeType.Removed: return 'removed';\n case DiffChangeType.Modified: return 'modified';\n case DiffChangeType.Unchanged: return 'unchanged';\n default: return '';\n }\n }\n\n formatValue(value: any, path: string): string {\n if (value === undefined) return 'undefined';\n if (value === null) return 'null';\n \n const isExpanded = !!this.expandedValuesMap[path];\n const shouldTruncate = this.truncateValues && !isExpanded;\n \n if (typeof value === 'string') {\n if (shouldTruncate && value.length > this.maxStringLength) {\n return `\"${value.substring(0, this.maxStringLength)}...\"`;\n }\n return `\"${value}\"`;\n }\n \n if (typeof value === 'object') {\n try {\n const json = JSON.stringify(value, null, 2);\n if (shouldTruncate && json.length > 200) {\n // Show a preview for objects\n const preview = JSON.stringify(value).substring(0, 50);\n return `${preview}... (${this.getObjectSize(value)} properties)`;\n }\n return json;\n } catch {\n return String(value);\n }\n }\n \n return String(value);\n }\n \n isValueTruncated(value: any, path: string): boolean {\n if (!this.truncateValues || this.expandedValuesMap[path]) {\n return false;\n }\n \n if (typeof value === 'string') {\n return value.length > this.maxStringLength;\n }\n \n if (typeof value === 'object' && value !== null) {\n try {\n const json = JSON.stringify(value, null, 2);\n return json.length > 200;\n } catch {\n return false;\n }\n }\n \n return false;\n }\n \n toggleValueExpansion(path: string, event: Event): void {\n event.stopPropagation();\n \n // Create a new object to trigger change detection\n this.expandedValuesMap = {\n ...this.expandedValuesMap,\n [path]: !this.expandedValuesMap[path]\n };\n \n // Force change detection\n this.cdr.markForCheck();\n }\n \n private getObjectSize(obj: any): number {\n if (Array.isArray(obj)) {\n return obj.length;\n }\n return Object.keys(obj).length;\n }\n\n copyToClipboard(text: string): void {\n navigator.clipboard.writeText(text).then(() => {\n // Could add a toast notification here\n });\n }\n \n isExpanded(path: string): boolean {\n return !!this.expandedValuesMap[path];\n }\n \n copyValueToClipboard(value: any, event: Event): void {\n event.stopPropagation();\n let textToCopy: string;\n \n if (value === undefined) {\n textToCopy = 'undefined';\n } else if (value === null) {\n textToCopy = 'null';\n } else if (typeof value === 'object') {\n try {\n textToCopy = JSON.stringify(value, null, 2);\n } catch {\n textToCopy = String(value);\n }\n } else {\n textToCopy = String(value);\n }\n \n navigator.clipboard.writeText(textToCopy).then(() => {\n // Could add a toast notification here\n });\n }\n\n exportDiff(): void {\n if (!this.diffResult) return;\n \n const blob = new Blob([JSON.stringify(this.diffResult, null, 2)], \n { type: 'application/json' });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = `diff-${new Date().toISOString()}.json`;\n a.click();\n URL.revokeObjectURL(url);\n }\n}","<div class=\"deep-diff-container\">\n <!-- Header -->\n <div class=\"diff-header\">\n <div class=\"header-content\">\n <h2 class=\"diff-title\">\n <i class=\"fa-solid fa-code-branch\"></i>\n {{ title }}\n </h2>\n <div class=\"header-actions\">\n <button class=\"btn-icon\" (click)=\"expandAllItems()\" title=\"Expand All\" aria-label=\"Expand All\">\n <i class=\"fa-solid fa-expand\"></i>\n </button>\n <button class=\"btn-icon\" (click)=\"collapseAllItems()\" title=\"Collapse All\" aria-label=\"Collapse All\">\n <i class=\"fa-solid fa-compress\"></i>\n </button>\n <button class=\"btn-icon\" (click)=\"exportDiff()\" title=\"Export Diff\" aria-label=\"Export Diff\" [disabled]=\"!diffResult\">\n <i class=\"fa-solid fa-download\"></i>\n </button>\n </div>\n </div>\n\n <!-- Summary -->\n @if (showSummary && diffResult) {\n <div class=\"diff-summary\">\n <div class=\"summary-item added\">\n <i class=\"fa-solid fa-plus-circle\"></i>\n <span class=\"count\">{{ diffResult.summary.added }}</span>\n <span class=\"label\">Added</span>\n </div>\n <div class=\"summary-item removed\">\n <i class=\"fa-solid fa-minus-circle\"></i>\n <span class=\"count\">{{ diffResult.summary.removed }}</span>\n <span class=\"label\">Removed</span>\n </div>\n <div class=\"summary-item modified\">\n <i class=\"fa-solid fa-edit\"></i>\n <span class=\"count\">{{ diffResult.summary.modified }}</span>\n <span class=\"label\">Modified</span>\n </div>\n @if (showUnchanged) {\n <div class=\"summary-item unchanged\">\n <i class=\"fa-solid fa-check-circle\"></i>\n <span class=\"count\">{{ diffResult.summary.unchanged }}</span>\n <span class=\"label\">Unchanged</span>\n </div>\n }\n </div>\n }\n </div>\n\n <!-- Filters -->\n <div class=\"diff-filters\">\n <div class=\"filter-search\">\n <i class=\"fa-solid fa-search\"></i>\n <input \n type=\"text\" \n [(ngModel)]=\"filter\" \n placeholder=\"Filter by path or description...\"\n class=\"filter-input\">\n </div>\n <div class=\"filter-types\">\n <button \n class=\"filter-btn\"\n [class.active]=\"filterType === 'all'\"\n (click)=\"filterType = 'all'\">\n All Changes\n </button>\n <button \n class=\"filter-btn added\"\n [class.active]=\"filterType === 'added'\"\n (click)=\"filterType = 'added'\">\n <i class=\"fa-solid fa-plus-circle\"></i>\n Added\n </button>\n <button \n class=\"filter-btn removed\"\n [class.active]=\"filterType === 'removed'\"\n (click)=\"filterType = 'removed'\">\n <i class=\"fa-solid fa-minus-circle\"></i>\n Removed\n </button>\n <button \n class=\"filter-btn modified\"\n [class.active]=\"filterType === 'modified'\"\n (click)=\"filterType = 'modified'\">\n <i class=\"fa-solid fa-edit\"></i>\n Modified\n </button>\n </div>\n </div>\n\n <!-- Diff Content -->\n <div class=\"diff-content\">\n @if (!diffResult) {\n <div class=\"empty-state\">\n <i class=\"fa-solid fa-code-compare fa-3x\"></i>\n <p>No data to compare</p>\n </div>\n } @else if (filteredItems.length === 0) {\n <div class=\"empty-state\">\n <i class=\"fa-solid fa-filter fa-3x\"></i>\n <p>No changes match your filter criteria</p>\n </div>\n } @else {\n <div class=\"diff-tree\">\n @for (item of filteredItems; track item.path) {\n <ng-container *ngTemplateOutlet=\"diffItem; context: { item: item, level: 0 }\"></ng-container>\n }\n </div>\n }\n </div>\n</div>\n\n<!-- Recursive Template for Diff Items -->\n<ng-template #diffItem let-item=\"item\" let-level=\"level\">\n <div class=\"diff-item\" [class]=\"getTypeClass(item.type)\" [style.padding-left.px]=\"level * 24\">\n <div class=\"item-header\" (click)=\"toggleItem(item)\">\n @if (item.children.length > 0) {\n <i class=\"fa-solid fa-chevron-right expand-icon\" [class.expanded]=\"item.isExpanded\"></i>\n } @else {\n <span class=\"expand-spacer\"></span>\n }\n \n <i class=\"fa-solid type-icon\" [ngClass]=\"getIcon(item.type)\"></i>\n \n <span class=\"item-path\">{{ item.path }}</span>\n \n <span class=\"item-description\">{{ item.description }}</span>\n \n <button class=\"btn-copy\" (click)=\"copyToClipboard(item.path); $event.stopPropagation()\" title=\"Copy path\" aria-label=\"Copy path\">\n <i class=\"fa-solid fa-copy\"></i>\n </button>\n </div>\n\n <!-- Value Details -->\n @if (item.type === 'modified') {\n <div class=\"value-details\">\n <div class=\"value-row old\">\n <span class=\"value-label\">Old:</span>\n <code class=\"value-content\">{{ formatValue(item.oldValue, item.path + '_old') }}</code>\n <button class=\"btn-copy\" (click)=\"copyValueToClipboard(item.oldValue, $event)\" title=\"Copy value\" aria-label=\"Copy value\">\n <i class=\"fa-solid fa-copy\"></i>\n </button>\n @if (isValueTruncated(item.oldValue, item.path + '_old') || isExpanded(item.path + '_old')) {\n <button class=\"btn-expand-value\" \n (click)=\"toggleValueExpansion(item.path + '_old', $event)\" \n title=\"{{ isExpanded(item.path + '_old') ? 'Collapse' : 'Expand' }}\"\n [attr.aria-label]=\"isExpanded(item.path + '_old') ? 'Collapse value' : 'Expand value'\">\n <i class=\"fa-solid\" \n [ngClass]=\"isExpanded(item.path + '_old') ? 'fa-compress-alt' : 'fa-expand-alt'\"></i>\n </button>\n }\n </div>\n <div class=\"value-row new\">\n <span class=\"value-label\">New:</span>\n <code class=\"value-content\">{{ formatValue(item.newValue, item.path + '_new') }}</code>\n <button class=\"btn-copy\" (click)=\"copyValueToClipboard(item.newValue, $event)\" title=\"Copy value\" aria-label=\"Copy value\">\n <i class=\"fa-solid fa-copy\"></i>\n </button>\n @if (isValueTruncated(item.newValue, item.path + '_new') || isExpanded(item.path + '_new')) {\n <button class=\"btn-expand-value\" \n (click)=\"toggleValueExpansion(item.path + '_new', $event)\"\n title=\"{{ isExpanded(item.path + '_new') ? 'Collapse' : 'Expand' }}\"\n [attr.aria-label]=\"isExpanded(item.path + '_new') ? 'Collapse value' : 'Expand value'\">\n <i class=\"fa-solid\" \n [ngClass]=\"isExpanded(item.path + '_new') ? 'fa-compress-alt' : 'fa-expand-alt'\"></i>\n </button>\n }\n </div>\n </div>\n } @else if (item.type === 'added') {\n <div class=\"value-details\">\n <div class=\"value-row new\">\n <span class=\"value-label\">Value:</span>\n <code class=\"value-content\">{{ formatValue(item.newValue, item.path) }}</code>\n <button class=\"btn-copy\" (click)=\"copyValueToClipboard(item.newValue, $event)\" title=\"Copy value\" aria-label=\"Copy value\">\n <i class=\"fa-solid fa-copy\"></i>\n </button>\n @if (isValueTruncated(item.newValue, item.path) || isExpanded(item.path)) {\n <button class=\"btn-expand-value\" \n (click)=\"toggleValueExpansion(item.path, $event)\"\n title=\"{{ isExpanded(item.path) ? 'Collapse' : 'Expand' }}\"\n [attr.aria-label]=\"isExpanded(item.path) ? 'Collapse value' : 'Expand value'\">\n <i class=\"fa-solid\" \n [ngClass]=\"isExpanded(item.path) ? 'fa-compress-alt' : 'fa-expand-alt'\"></i>\n </button>\n }\n </div>\n </div>\n } @else if (item.type === 'removed') {\n <div class=\"value-details\">\n <div class=\"value-row old\">\n <span class=\"value-label\">Value:</span>\n <code class=\"value-content\">{{ formatValue(item.oldValue, item.path) }}</code>\n <button class=\"btn-copy\" (click)=\"copyValueToClipboard(item.oldValue, $event)\" title=\"Copy value\" aria-label=\"Copy value\">\n <i class=\"fa-solid fa-copy\"></i>\n </button>\n @if (isValueTruncated(item.oldValue, item.path) || isExpanded(item.path)) {\n <button class=\"btn-expand-value\" \n (click)=\"toggleValueExpansion(item.path, $event)\"\n title=\"{{ isExpanded(item.path) ? 'Collapse' : 'Expand' }}\"\n [attr.aria-label]=\"isExpanded(item.path) ? 'Collapse value' : 'Expand value'\">\n <i class=\"fa-solid\" \n [ngClass]=\"isExpanded(item.path) ? 'fa-compress-alt' : 'fa-expand-alt'\"></i>\n </button>\n }\n </div>\n </div>\n }\n\n <!-- Children -->\n @if (item.isExpanded && item.children.length > 0) {\n <div class=\"item-children\">\n @for (child of item.children; track child.path) {\n <ng-container *ngTemplateOutlet=\"diffItem; context: { item: child, level: level + 1 }\"></ng-container>\n }\n </div>\n }\n </div>\n</ng-template>"]}
|
|
1
|
+
{"version":3,"file":"deep-diff.component.js","sourceRoot":"","sources":["../../src/lib/deep-diff.component.ts","../../src/lib/deep-diff.component.html"],"names":[],"mappings":"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","sourcesContent":["import { Component, Input, OnInit, ChangeDetectorRef, ChangeDetectionStrategy } from '@angular/core';\nimport { DeepDiffer, DeepDiffResult, DiffChangeType, DiffChange } from '@memberjunction/global';\n\nexport interface DeepDiffItem extends DiffChange {\n level: number;\n isExpanded: boolean;\n children: DeepDiffItem[];\n parentPath: string;\n}\n\n@Component({\n standalone: false,\n selector: 'mj-deep-diff',\n templateUrl: './deep-diff.component.html',\n styleUrls: ['./deep-diff.component.css'],\n changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class DeepDiffComponent implements OnInit {\n private _oldValue: any;\n private _newValue: any;\n private _showUnchanged: boolean = false;\n private _maxDepth: number = 10;\n private _maxStringLength: number = 100;\n private _treatNullAsUndefined: boolean = false;\n\n @Input()\n get oldValue(): any {\n return this._oldValue;\n }\n set oldValue(value: any) {\n this._oldValue = value;\n this.generateDiff();\n }\n\n @Input()\n get newValue(): any {\n return this._newValue;\n }\n set newValue(value: any) {\n this._newValue = value;\n this.generateDiff();\n }\n\n @Input()\n get showUnchanged(): boolean {\n return this._showUnchanged;\n }\n set showUnchanged(value: boolean) {\n this._showUnchanged = value;\n this.updateDifferConfig();\n this.generateDiff();\n }\n\n @Input()\n get maxDepth(): number {\n return this._maxDepth;\n }\n set maxDepth(value: number) {\n this._maxDepth = value;\n this.updateDifferConfig();\n this.generateDiff();\n }\n\n @Input()\n get maxStringLength(): number {\n return this._maxStringLength;\n }\n set maxStringLength(value: number) {\n this._maxStringLength = value;\n this.updateDifferConfig();\n this.generateDiff();\n }\n\n @Input()\n get treatNullAsUndefined(): boolean {\n return this._treatNullAsUndefined;\n }\n set treatNullAsUndefined(value: boolean) {\n this._treatNullAsUndefined = value;\n this.updateDifferConfig();\n this.generateDiff();\n }\n\n @Input() title: string = 'Deep Diff Analysis';\n @Input() showSummary: boolean = true;\n @Input() expandAll: boolean = false;\n @Input() truncateValues: boolean = true;\n\n diffResult: DeepDiffResult | null = null;\n diffItems: DeepDiffItem[] = [];\n\n private _filter: string = '';\n get filter(): string {\n return this._filter;\n }\n set filter(value: string) {\n this._filter = value;\n this.recomputeFilteredItems();\n }\n\n private _filterType: 'all' | 'added' | 'removed' | 'modified' | 'unchanged' = 'all';\n get filterType(): 'all' | 'added' | 'removed' | 'modified' | 'unchanged' {\n return this._filterType;\n }\n set filterType(value: 'all' | 'added' | 'removed' | 'modified' | 'unchanged') {\n this._filterType = value;\n this.recomputeFilteredItems();\n }\n\n /**\n * Precomputed filtered diff tree. Recomputed only when the source data\n * (`diffItems`) or the filter inputs (`filter` / `filterType`) change —\n * never on every change-detection cycle. Bound directly by the template's\n * `@for` and empty-state length check, which preserves `@for` referential\n * stability between CD passes (the recursive walk no longer runs >= 2x/CD).\n */\n filteredItems: DeepDiffItem[] = [];\n\n expandedValuesMap: { [key: string]: boolean } = {};\n\n /**\n * Memoization cache for {@link formatValue} / {@link isValueTruncated}. Keyed\n * by `${path}|${expanded}` so the expensive `JSON.stringify` of object values\n * runs at most once per (path, expansion-state) instead of on every\n * change-detection cycle (the template binds these in a recursive `@for`).\n * Invalidated whenever the source data is regenerated.\n */\n private formatValueCache = new Map<string, string>();\n private truncatedCache = new Map<string, boolean>();\n\n private differ: DeepDiffer;\n\n constructor(private cdr: ChangeDetectorRef) {\n this.differ = new DeepDiffer({\n includeUnchanged: false,\n maxDepth: this.maxDepth,\n maxStringLength: this.maxStringLength,\n treatNullAsUndefined: this.treatNullAsUndefined\n });\n }\n\n ngOnInit(): void {\n this.generateDiff();\n }\n\n\n private updateDifferConfig(): void {\n this.differ.updateConfig({\n includeUnchanged: this.showUnchanged,\n maxDepth: this.maxDepth,\n maxStringLength: this.maxStringLength,\n treatNullAsUndefined: this.treatNullAsUndefined\n });\n }\n\n private generateDiff(): void {\n if (this.oldValue === undefined && this.newValue === undefined) {\n this.formatValueCache.clear();\n this.truncatedCache.clear();\n this.diffResult = null;\n this.diffItems = [];\n this.recomputeFilteredItems();\n return;\n }\n\n this.formatValueCache.clear();\n this.truncatedCache.clear();\n this.diffResult = this.differ.diff(this.oldValue, this.newValue);\n this.diffItems = this.buildHierarchicalItems(this.diffResult.changes);\n\n if (this.expandAll) {\n this.expandAllItems();\n }\n\n this.recomputeFilteredItems();\n }\n\n private buildHierarchicalItems(changes: DiffChange[]): DeepDiffItem[] {\n const rootItems: DeepDiffItem[] = [];\n const itemMap = new Map<string, DeepDiffItem>();\n\n // Sort by path to ensure parents come before children\n const sortedChanges = [...changes].sort((a, b) => {\n const aDepth = a.path.split('.').length;\n const bDepth = b.path.split('.').length;\n if (aDepth !== bDepth) return aDepth - bDepth;\n return a.path.localeCompare(b.path);\n });\n\n for (const change of sortedChanges) {\n const pathParts = change.path === 'root' ? [] : change.path.split('.');\n const level = pathParts.length;\n const parentPath = pathParts.slice(0, -1).join('.');\n \n const item: DeepDiffItem = {\n ...change,\n level,\n isExpanded: level === 0, // Expand only root level items by default\n children: [],\n parentPath\n };\n\n itemMap.set(change.path, item);\n\n if (parentPath && itemMap.has(parentPath)) {\n itemMap.get(parentPath)!.children.push(item);\n } else {\n rootItems.push(item);\n }\n }\n\n return rootItems;\n }\n\n toggleItem(item: DeepDiffItem): void {\n item.isExpanded = !item.isExpanded;\n this.cdr.markForCheck();\n }\n\n expandAllItems(): void {\n const expand = (items: DeepDiffItem[]) => {\n for (const item of items) {\n item.isExpanded = true;\n if (item.children.length > 0) {\n expand(item.children);\n }\n }\n };\n expand(this.diffItems);\n this.cdr.markForCheck();\n }\n\n collapseAllItems(): void {\n const collapse = (items: DeepDiffItem[]) => {\n for (const item of items) {\n item.isExpanded = false;\n if (item.children.length > 0) {\n collapse(item.children);\n }\n }\n };\n collapse(this.diffItems);\n this.cdr.markForCheck();\n }\n\n /**\n * Recomputes {@link filteredItems} from the current `diffItems` + filter\n * inputs. Called from `generateDiff()` (data change) and from the\n * `filter` / `filterType` setters (filter change) — NOT per CD cycle.\n */\n private recomputeFilteredItems(): void {\n if (!this.filter && this.filterType === 'all') {\n this.filteredItems = this.diffItems;\n this.cdr.markForCheck();\n return;\n }\n\n const filterFn = (item: DeepDiffItem): boolean => {\n const matchesType = this.filterType === 'all' || \n (this.filterType === 'added' && item.type === DiffChangeType.Added) ||\n (this.filterType === 'removed' && item.type === DiffChangeType.Removed) ||\n (this.filterType === 'modified' && item.type === DiffChangeType.Modified) ||\n (this.filterType === 'unchanged' && item.type === DiffChangeType.Unchanged);\n const matchesText = !this.filter || \n item.path.toLowerCase().includes(this.filter.toLowerCase()) ||\n item.description.toLowerCase().includes(this.filter.toLowerCase());\n \n return matchesType && matchesText;\n };\n\n const filterRecursive = (items: DeepDiffItem[]): DeepDiffItem[] => {\n return items.reduce((acc, item) => {\n const childMatches = filterRecursive(item.children);\n if (filterFn(item) || childMatches.length > 0) {\n acc.push({\n ...item,\n children: childMatches,\n isExpanded: item.isExpanded // Preserve the original expanded state\n });\n }\n return acc;\n }, [] as DeepDiffItem[]);\n };\n\n this.filteredItems = filterRecursive(this.diffItems);\n this.cdr.markForCheck();\n }\n\n getIcon(type: DiffChangeType): string {\n switch (type) {\n case DiffChangeType.Added: return 'fa-plus-circle';\n case DiffChangeType.Removed: return 'fa-minus-circle';\n case DiffChangeType.Modified: return 'fa-edit';\n case DiffChangeType.Unchanged: return 'fa-check-circle';\n default: return 'fa-question-circle';\n }\n }\n\n getTypeClass(type: DiffChangeType): string {\n switch (type) {\n case DiffChangeType.Added: return 'added';\n case DiffChangeType.Removed: return 'removed';\n case DiffChangeType.Modified: return 'modified';\n case DiffChangeType.Unchanged: return 'unchanged';\n default: return '';\n }\n }\n\n formatValue(value: any, path: string): string {\n if (value === undefined) return 'undefined';\n if (value === null) return 'null';\n\n const isExpanded = !!this.expandedValuesMap[path];\n const cacheKey = `${path}|${isExpanded ? 1 : 0}`;\n const cached = this.formatValueCache.get(cacheKey);\n if (cached !== undefined) return cached;\n\n const formatted = this.computeFormattedValue(value, isExpanded);\n this.formatValueCache.set(cacheKey, formatted);\n return formatted;\n }\n\n private computeFormattedValue(value: any, isExpanded: boolean): string {\n const shouldTruncate = this.truncateValues && !isExpanded;\n\n if (typeof value === 'string') {\n if (shouldTruncate && value.length > this.maxStringLength) {\n return `\"${value.substring(0, this.maxStringLength)}...\"`;\n }\n return `\"${value}\"`;\n }\n \n if (typeof value === 'object') {\n try {\n const json = JSON.stringify(value, null, 2);\n if (shouldTruncate && json.length > 200) {\n // Show a preview for objects\n const preview = JSON.stringify(value).substring(0, 50);\n return `${preview}... (${this.getObjectSize(value)} properties)`;\n }\n return json;\n } catch {\n return String(value);\n }\n }\n \n return String(value);\n }\n \n isValueTruncated(value: any, path: string): boolean {\n if (!this.truncateValues || this.expandedValuesMap[path]) {\n return false;\n }\n\n const cacheKey = `${path}|0`;\n const cached = this.truncatedCache.get(cacheKey);\n if (cached !== undefined) return cached;\n\n const result = this.computeValueTruncated(value);\n this.truncatedCache.set(cacheKey, result);\n return result;\n }\n\n private computeValueTruncated(value: any): boolean {\n if (typeof value === 'string') {\n return value.length > this.maxStringLength;\n }\n \n if (typeof value === 'object' && value !== null) {\n try {\n const json = JSON.stringify(value, null, 2);\n return json.length > 200;\n } catch {\n return false;\n }\n }\n \n return false;\n }\n \n toggleValueExpansion(path: string, event: Event): void {\n event.stopPropagation();\n \n // Create a new object to trigger change detection\n this.expandedValuesMap = {\n ...this.expandedValuesMap,\n [path]: !this.expandedValuesMap[path]\n };\n \n // Force change detection\n this.cdr.markForCheck();\n }\n \n private getObjectSize(obj: any): number {\n if (Array.isArray(obj)) {\n return obj.length;\n }\n return Object.keys(obj).length;\n }\n\n copyToClipboard(text: string): void {\n navigator.clipboard.writeText(text).then(() => {\n // Could add a toast notification here\n });\n }\n \n isExpanded(path: string): boolean {\n return !!this.expandedValuesMap[path];\n }\n \n copyValueToClipboard(value: any, event: Event): void {\n event.stopPropagation();\n let textToCopy: string;\n \n if (value === undefined) {\n textToCopy = 'undefined';\n } else if (value === null) {\n textToCopy = 'null';\n } else if (typeof value === 'object') {\n try {\n textToCopy = JSON.stringify(value, null, 2);\n } catch {\n textToCopy = String(value);\n }\n } else {\n textToCopy = String(value);\n }\n \n navigator.clipboard.writeText(textToCopy).then(() => {\n // Could add a toast notification here\n });\n }\n\n exportDiff(): void {\n if (!this.diffResult) return;\n \n const blob = new Blob([JSON.stringify(this.diffResult, null, 2)], \n { type: 'application/json' });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = `diff-${new Date().toISOString()}.json`;\n a.click();\n URL.revokeObjectURL(url);\n }\n}","<div class=\"deep-diff-container\">\n <!-- Header -->\n <div class=\"diff-header\">\n <div class=\"header-content\">\n <h2 class=\"diff-title\">\n <i class=\"fa-solid fa-code-branch\"></i>\n {{ title }}\n </h2>\n <div class=\"header-actions\">\n <button class=\"btn-icon\" (click)=\"expandAllItems()\" title=\"Expand All\" aria-label=\"Expand All\">\n <i class=\"fa-solid fa-expand\"></i>\n </button>\n <button class=\"btn-icon\" (click)=\"collapseAllItems()\" title=\"Collapse All\" aria-label=\"Collapse All\">\n <i class=\"fa-solid fa-compress\"></i>\n </button>\n <button class=\"btn-icon\" (click)=\"exportDiff()\" title=\"Export Diff\" aria-label=\"Export Diff\" [disabled]=\"!diffResult\">\n <i class=\"fa-solid fa-download\"></i>\n </button>\n </div>\n </div>\n\n <!-- Summary -->\n @if (showSummary && diffResult) {\n <div class=\"diff-summary\">\n <div class=\"summary-item added\">\n <i class=\"fa-solid fa-plus-circle\"></i>\n <span class=\"count\">{{ diffResult.summary.added }}</span>\n <span class=\"label\">Added</span>\n </div>\n <div class=\"summary-item removed\">\n <i class=\"fa-solid fa-minus-circle\"></i>\n <span class=\"count\">{{ diffResult.summary.removed }}</span>\n <span class=\"label\">Removed</span>\n </div>\n <div class=\"summary-item modified\">\n <i class=\"fa-solid fa-edit\"></i>\n <span class=\"count\">{{ diffResult.summary.modified }}</span>\n <span class=\"label\">Modified</span>\n </div>\n @if (showUnchanged) {\n <div class=\"summary-item unchanged\">\n <i class=\"fa-solid fa-check-circle\"></i>\n <span class=\"count\">{{ diffResult.summary.unchanged }}</span>\n <span class=\"label\">Unchanged</span>\n </div>\n }\n </div>\n }\n </div>\n\n <!-- Filters -->\n <div class=\"diff-filters\">\n <div class=\"filter-search\">\n <i class=\"fa-solid fa-search\"></i>\n <input \n type=\"text\" \n [(ngModel)]=\"filter\" \n placeholder=\"Filter by path or description...\"\n class=\"filter-input\">\n </div>\n <div class=\"filter-types\">\n <button \n class=\"filter-btn\"\n [class.active]=\"filterType === 'all'\"\n (click)=\"filterType = 'all'\">\n All Changes\n </button>\n <button \n class=\"filter-btn added\"\n [class.active]=\"filterType === 'added'\"\n (click)=\"filterType = 'added'\">\n <i class=\"fa-solid fa-plus-circle\"></i>\n Added\n </button>\n <button \n class=\"filter-btn removed\"\n [class.active]=\"filterType === 'removed'\"\n (click)=\"filterType = 'removed'\">\n <i class=\"fa-solid fa-minus-circle\"></i>\n Removed\n </button>\n <button \n class=\"filter-btn modified\"\n [class.active]=\"filterType === 'modified'\"\n (click)=\"filterType = 'modified'\">\n <i class=\"fa-solid fa-edit\"></i>\n Modified\n </button>\n </div>\n </div>\n\n <!-- Diff Content -->\n <div class=\"diff-content\">\n @if (!diffResult) {\n <div class=\"empty-state\">\n <i class=\"fa-solid fa-code-compare fa-3x\"></i>\n <p>No data to compare</p>\n </div>\n } @else if (filteredItems.length === 0) {\n <div class=\"empty-state\">\n <i class=\"fa-solid fa-filter fa-3x\"></i>\n <p>No changes match your filter criteria</p>\n </div>\n } @else {\n <div class=\"diff-tree\">\n @for (item of filteredItems; track item.path) {\n <ng-container *ngTemplateOutlet=\"diffItem; context: { item: item, level: 0 }\"></ng-container>\n }\n </div>\n }\n </div>\n</div>\n\n<!-- Recursive Template for Diff Items -->\n<ng-template #diffItem let-item=\"item\" let-level=\"level\">\n <div class=\"diff-item\" [class]=\"getTypeClass(item.type)\" [style.padding-left.px]=\"level * 24\">\n <div class=\"item-header\" (click)=\"toggleItem(item)\">\n @if (item.children.length > 0) {\n <i class=\"fa-solid fa-chevron-right expand-icon\" [class.expanded]=\"item.isExpanded\"></i>\n } @else {\n <span class=\"expand-spacer\"></span>\n }\n \n <i class=\"fa-solid type-icon\" [ngClass]=\"getIcon(item.type)\"></i>\n \n <span class=\"item-path\">{{ item.path }}</span>\n \n <span class=\"item-description\">{{ item.description }}</span>\n \n <button class=\"btn-copy\" (click)=\"copyToClipboard(item.path); $event.stopPropagation()\" title=\"Copy path\" aria-label=\"Copy path\">\n <i class=\"fa-solid fa-copy\"></i>\n </button>\n </div>\n\n <!-- Value Details -->\n @if (item.type === 'modified') {\n <div class=\"value-details\">\n <div class=\"value-row old\">\n <span class=\"value-label\">Old:</span>\n <code class=\"value-content\">{{ formatValue(item.oldValue, item.path + '_old') }}</code>\n <button class=\"btn-copy\" (click)=\"copyValueToClipboard(item.oldValue, $event)\" title=\"Copy value\" aria-label=\"Copy value\">\n <i class=\"fa-solid fa-copy\"></i>\n </button>\n @if (isValueTruncated(item.oldValue, item.path + '_old') || isExpanded(item.path + '_old')) {\n <button class=\"btn-expand-value\" \n (click)=\"toggleValueExpansion(item.path + '_old', $event)\" \n title=\"{{ isExpanded(item.path + '_old') ? 'Collapse' : 'Expand' }}\"\n [attr.aria-label]=\"isExpanded(item.path + '_old') ? 'Collapse value' : 'Expand value'\">\n <i class=\"fa-solid\" \n [ngClass]=\"isExpanded(item.path + '_old') ? 'fa-compress-alt' : 'fa-expand-alt'\"></i>\n </button>\n }\n </div>\n <div class=\"value-row new\">\n <span class=\"value-label\">New:</span>\n <code class=\"value-content\">{{ formatValue(item.newValue, item.path + '_new') }}</code>\n <button class=\"btn-copy\" (click)=\"copyValueToClipboard(item.newValue, $event)\" title=\"Copy value\" aria-label=\"Copy value\">\n <i class=\"fa-solid fa-copy\"></i>\n </button>\n @if (isValueTruncated(item.newValue, item.path + '_new') || isExpanded(item.path + '_new')) {\n <button class=\"btn-expand-value\" \n (click)=\"toggleValueExpansion(item.path + '_new', $event)\"\n title=\"{{ isExpanded(item.path + '_new') ? 'Collapse' : 'Expand' }}\"\n [attr.aria-label]=\"isExpanded(item.path + '_new') ? 'Collapse value' : 'Expand value'\">\n <i class=\"fa-solid\" \n [ngClass]=\"isExpanded(item.path + '_new') ? 'fa-compress-alt' : 'fa-expand-alt'\"></i>\n </button>\n }\n </div>\n </div>\n } @else if (item.type === 'added') {\n <div class=\"value-details\">\n <div class=\"value-row new\">\n <span class=\"value-label\">Value:</span>\n <code class=\"value-content\">{{ formatValue(item.newValue, item.path) }}</code>\n <button class=\"btn-copy\" (click)=\"copyValueToClipboard(item.newValue, $event)\" title=\"Copy value\" aria-label=\"Copy value\">\n <i class=\"fa-solid fa-copy\"></i>\n </button>\n @if (isValueTruncated(item.newValue, item.path) || isExpanded(item.path)) {\n <button class=\"btn-expand-value\" \n (click)=\"toggleValueExpansion(item.path, $event)\"\n title=\"{{ isExpanded(item.path) ? 'Collapse' : 'Expand' }}\"\n [attr.aria-label]=\"isExpanded(item.path) ? 'Collapse value' : 'Expand value'\">\n <i class=\"fa-solid\" \n [ngClass]=\"isExpanded(item.path) ? 'fa-compress-alt' : 'fa-expand-alt'\"></i>\n </button>\n }\n </div>\n </div>\n } @else if (item.type === 'removed') {\n <div class=\"value-details\">\n <div class=\"value-row old\">\n <span class=\"value-label\">Value:</span>\n <code class=\"value-content\">{{ formatValue(item.oldValue, item.path) }}</code>\n <button class=\"btn-copy\" (click)=\"copyValueToClipboard(item.oldValue, $event)\" title=\"Copy value\" aria-label=\"Copy value\">\n <i class=\"fa-solid fa-copy\"></i>\n </button>\n @if (isValueTruncated(item.oldValue, item.path) || isExpanded(item.path)) {\n <button class=\"btn-expand-value\" \n (click)=\"toggleValueExpansion(item.path, $event)\"\n title=\"{{ isExpanded(item.path) ? 'Collapse' : 'Expand' }}\"\n [attr.aria-label]=\"isExpanded(item.path) ? 'Collapse value' : 'Expand value'\">\n <i class=\"fa-solid\" \n [ngClass]=\"isExpanded(item.path) ? 'fa-compress-alt' : 'fa-expand-alt'\"></i>\n </button>\n }\n </div>\n </div>\n }\n\n <!-- Children -->\n @if (item.isExpanded && item.children.length > 0) {\n <div class=\"item-children\">\n @for (child of item.children; track child.path) {\n <ng-container *ngTemplateOutlet=\"diffItem; context: { item: child, level: level + 1 }\"></ng-container>\n }\n </div>\n }\n </div>\n</ng-template>"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/ng-deep-diff",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.41.0",
|
|
4
4
|
"description": "MemberJunction: Generic Angular component to display the differences between two objects, using the non-visual functionality from the @memberjunction/global package",
|
|
5
5
|
"main": "./dist/public-api.js",
|
|
6
6
|
"typings": "./dist/public-api.d.ts",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"/dist"
|
|
9
9
|
],
|
|
10
10
|
"scripts": {
|
|
11
|
-
"test": "
|
|
11
|
+
"test": "vitest run",
|
|
12
12
|
"build": "ngc",
|
|
13
13
|
"test:watch": "vitest"
|
|
14
14
|
},
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@angular/forms": "21.1.3",
|
|
29
|
-
"@memberjunction/core": "5.
|
|
30
|
-
"@memberjunction/global": "5.
|
|
29
|
+
"@memberjunction/core": "5.41.0",
|
|
30
|
+
"@memberjunction/global": "5.41.0",
|
|
31
31
|
"tslib": "^2.8.1"
|
|
32
32
|
},
|
|
33
33
|
"sideEffects": false,
|