From 3859545dbdcd9ea24a1ad887e2178a5ce2f10a66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Utku=20Aky=C3=BCz?= Date: Tue, 16 Sep 2025 11:21:38 +0300 Subject: [PATCH 1/4] feature: accessible difference data with callback Also this commit enables passing custom differ in case of need And now exporting type DiffResult and Differ class --- .../components/VirtualizedDiffViewer.tsx | 25 ++++++++++++++++++- src/components/DiffViewer/types/index.ts | 4 ++- .../DiffViewer/utils/json-diff/diff-hash.ts | 11 ++++++++ src/index.ts | 2 +- 4 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 src/components/DiffViewer/utils/json-diff/diff-hash.ts diff --git a/src/components/DiffViewer/components/VirtualizedDiffViewer.tsx b/src/components/DiffViewer/components/VirtualizedDiffViewer.tsx index 6745d9f..7279cdd 100644 --- a/src/components/DiffViewer/components/VirtualizedDiffViewer.tsx +++ b/src/components/DiffViewer/components/VirtualizedDiffViewer.tsx @@ -1,3 +1,4 @@ +import type { DiffResult } from "json-diff-kit"; import type { VariableSizeList as List } from "react-window"; import { Differ } from "json-diff-kit"; @@ -7,6 +8,7 @@ import type { DiffRowOrCollapsed, SegmentItem, VirtualizedDiffViewerProps } from import "../styles/JsonDiffCustomTheme.css"; import { useSearch } from "../hooks/useSearch"; +import { fastHash } from "../utils/json-diff/diff-hash"; import { expandSegment, hasExpandedSegments, hideAllSegments } from "../utils/json-diff/segment-util"; import { buildViewFromSegments, generateSegments } from "../utils/preprocessDiff"; import { DiffMinimap } from "./DiffMinimap"; @@ -21,6 +23,8 @@ export const VirtualizedDiffViewer: React.FC = ({ leftTitle, rightTitle, hideSearch, + customDiffer, + getDiffData, showSingleMinimap, onSearchMatch, differOptions, @@ -31,8 +35,10 @@ export const VirtualizedDiffViewer: React.FC = ({ }) => { const outerRef = useRef(null); const listRef = useRef(null); + const getDiffDataRef = useRef(); + const lastSent = useRef(); - const differ = useMemo( + const differ = customDiffer ?? useMemo( () => new Differ({ detectCircular: true, @@ -101,6 +107,23 @@ export const VirtualizedDiffViewer: React.FC = ({ setRightView(rightBuilt); }, [segments, rawLeftDiff, rawRightDiff]); + useEffect(() => { + getDiffDataRef.current = getDiffData; + }, [getDiffData]); + + useEffect(() => { + if (!getDiffDataRef.current) + return; + + const data: [DiffResult[], DiffResult[]] = [rawLeftDiff, rawRightDiff]; + const hash = fastHash(data); + + if (lastSent.current !== hash) { + lastSent.current = hash; + getDiffDataRef.current(data); + } + }, [rawLeftDiff, rawRightDiff]); + return (
diff --git a/src/components/DiffViewer/types/index.ts b/src/components/DiffViewer/types/index.ts index c8544a9..c01f5a7 100644 --- a/src/components/DiffViewer/types/index.ts +++ b/src/components/DiffViewer/types/index.ts @@ -1,4 +1,4 @@ -import type { DifferOptions, DiffResult, InlineDiffOptions } from "json-diff-kit"; +import type { Differ, DifferOptions, DiffResult, InlineDiffOptions } from "json-diff-kit"; export type DiffRow = { originalIndex: number; @@ -41,7 +41,9 @@ export type VirtualizedDiffViewerProps = { leftTitle?: string; rightTitle?: string; onSearchMatch?: (index: number) => void; + getDiffData?: (diffData: [DiffResult[], DiffResult[]]) => void; differOptions?: DifferOptions; + customDiffer?: Differ; showSingleMinimap?: boolean; className?: string; miniMapWidth?: number; diff --git a/src/components/DiffViewer/utils/json-diff/diff-hash.ts b/src/components/DiffViewer/utils/json-diff/diff-hash.ts new file mode 100644 index 0000000..6613349 --- /dev/null +++ b/src/components/DiffViewer/utils/json-diff/diff-hash.ts @@ -0,0 +1,11 @@ +import type { DiffResult } from "json-diff-kit"; + +export function fastHash(diff: [DiffResult[], DiffResult[]]) { + let hash = 0; + for (const arr of diff) { + for (const row of arr) { + hash = (hash + row.text.length + row.type.charCodeAt(0)) >>> 0; + } + } + return hash; +} diff --git a/src/index.ts b/src/index.ts index 3728981..7e0fd61 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,2 @@ -// Buttons export { default as VirtualDiffViewer } from "./components/DiffViewer"; +export { Differ, type DiffResult } from "json-diff-kit"; From 01139c7e987886fdf249b5eeb08f0d6f6a5c4e5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Utku=20Aky=C3=BCz?= Date: Tue, 16 Sep 2025 11:28:04 +0300 Subject: [PATCH 2/4] refactor: hash logic updated --- .../DiffViewer/utils/json-diff/diff-hash.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/components/DiffViewer/utils/json-diff/diff-hash.ts b/src/components/DiffViewer/utils/json-diff/diff-hash.ts index 6613349..f1bb4d3 100644 --- a/src/components/DiffViewer/utils/json-diff/diff-hash.ts +++ b/src/components/DiffViewer/utils/json-diff/diff-hash.ts @@ -1,11 +1,18 @@ import type { DiffResult } from "json-diff-kit"; export function fastHash(diff: [DiffResult[], DiffResult[]]) { - let hash = 0; + let hash = 5381; // djb2 seed for (const arr of diff) { for (const row of arr) { - hash = (hash + row.text.length + row.type.charCodeAt(0)) >>> 0; + const text = row.text; + for (let i = 0; i < text.length; i++) { + hash = ((hash << 5) + hash) + text.charCodeAt(i); + } + const type = row.type; + for (let i = 0; i < type.length; i++) { + hash = ((hash << 5) + hash) + type.charCodeAt(i); + } } } - return hash; + return hash >>> 0; } From e03b90fc8d7d5aea969c65538d5bd806b99922b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Utku=20Aky=C3=BCz?= Date: Tue, 16 Sep 2025 14:19:33 +0300 Subject: [PATCH 3/4] chore: version and readme updates --- README.md | 71 +++++++++++++++++++++++++++++++++++------------ demo/package.json | 2 +- package.json | 2 +- 3 files changed, 55 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 1e0d94d..b5fb577 100644 --- a/README.md +++ b/README.md @@ -49,9 +49,9 @@ The theme is fully customizable, all colors can be changed. (And soon new themes Modify DifferOptions and InlineDiffOptions and see the output. -Dual Minimap is defaultly shown, to hide middle minimap, just pass ShowSingleMinimap prop to Viewer. +Dual Minimap is defaultly shown, to hide middle minimap, just pass `ShowSingleMinimap` prop to Viewer. -To change Diff methods please see DifferOptions. By default virtual-react-json-diff uses following configuration. +To change Diff methods please see `DifferOptions`. By default `virtual-react-json-diff` uses following configuration. ``` new Differ({ @@ -67,7 +67,6 @@ new Differ({ Simply pass your json objects into Viewer Component. It will find differences and show. ``` -import React from "react"; import { VirtualDiffViewer } from "virtual-react-json-diff"; const oldData = { name: "Alice", age: 25 }; @@ -85,6 +84,39 @@ export default function App() { } ``` +--- + +If you need to see or make some calculations on difference objects, you can get the diff data using `getDifferData` callback prop + +``` +import { type DiffResult, VirtualDiffViewer } from "virtual-react-json-diff"; + +const [differData, setDifferData] = useState<[DiffResult[], DiffResult[]]>(); + + setDifferData(diffData)} /> +``` + +Or if you have a custom Differ or a custom viewer, you can import `Differ` class to create diff objects using your own differ. Moreover you can pass that differ to `VirtualizedDiffViewer`. + +p.s. This is not recommended because you can modify all variables in Differ using `differOptions` prop in Viewer. + +``` +import { Differ, VirtualDiffViewer } from "virtual-react-json-diff"; +--- + const differOptions: DifferOptions = { + showModifications: config.showModifications, + arrayDiffMethod: config.arrayDiffMethod, + }; + const differ = new Differ(differOptions); + +--- + +// Pass it into Viewer with 'customDiffer' prop + +``` + +--- + The component exposes a root container with the class: ``` @@ -95,21 +127,24 @@ You can pass your own class name via the className prop to apply custom themes. ## Props -| Prop | Type | Default | Description | -| ------------------- | ------------------------- | ------------------ | ---------------------------------------------------------------------- | -| `oldValue` | `object` | — | The original JSON object to compare (left side). | -| `newValue` | `object` | — | The updated JSON object to compare (right side). | -| `height` | `number` | — | Height of the diff viewer in pixels. | -| `hideSearch` | `boolean` | `false` | Hides the search bar if set to `true`. | -| `searchTerm` | `string` | `""` | Initial search keyword to highlight within the diff. | -| `leftTitle` | `string` | — | Optional title to display above the left diff panel. | -| `rightTitle` | `string` | — | Optional title to display above the right diff panel. | -| `onSearchMatch` | `(index: number) => void` | — | Callback fired when a search match is found. Receives the match index. | -| `differOptions` | `DifferOptions` | `Given Above` | Advanced options passed to the diffing engine. | -| `showSingleMinimap` | `boolean` | `false` | If `true`, shows only one minimap instead of two. | -| `className` | `string` | — | Custom CSS class for styling the viewer container. | -| `miniMapWidth` | `number` | `40` | Width of each minimap in pixels. | -| `inlineDiffOptions` | `InlineDiffOptions` | `{'mode': 'char'}` | Options for fine-tuning inline diff rendering. | +| Prop | Type | Default | Description | +| ------------------- | -------------------------------------------------- | ------------------ | ---------------------------------------------------------------------- | +| `oldValue` | `object` | — | The original JSON object to compare (left side). | +| `newValue` | `object` | — | The updated JSON object to compare (right side). | +| `height` | `number` | — | Height of the diff viewer in pixels. | +| `hideSearch` | `boolean` | `false` | Hides the search bar if set to `true`. | +| `searchTerm` | `string` | `""` | Initial search keyword to highlight within the diff. | +| `leftTitle` | `string` | — | Optional title to display above the left diff panel. | +| `rightTitle` | `string` | — | Optional title to display above the right diff panel. | +| `onSearchMatch` | `(index: number) => void` | — | Callback fired when a search match is found. Receives the match index. | +| `differOptions` | `DifferOptions` | `Given Above` | Advanced options passed to the diffing engine. | +| `showSingleMinimap` | `boolean` | `false` | If `true`, shows only one minimap instead of two. | +| `className` | `string` | — | Custom CSS class for styling the viewer container. | +| `overScanCount` | `number` | `28` | Number of rendered rows outside of the viewport for virtualization | +| `miniMapWidth` | `number` | `40` | Width of each minimap in pixels. | +| `inlineDiffOptions` | `InlineDiffOptions` | `{'mode': 'char'}` | Options for fine-tuning inline diff rendering. | +| `getDiffData` | `(diffData: [DiffResult[], DiffResult[]]) => void` | - | Get difference data and make operations | +| `customDiffer` | `Differ` | - | Pass custom differ - not recommended | ## 🙌 Acknowledgements diff --git a/demo/package.json b/demo/package.json index 1c6152d..965edb9 100644 --- a/demo/package.json +++ b/demo/package.json @@ -14,7 +14,7 @@ "react-ace": "^14.0.1", "react-dom": "^18.0.0", "react-json-editor-ajrm": "^2.5.14", - "virtual-react-json-diff": "^1.0.12" + "virtual-react-json-diff": "^1.0.13" }, "devDependencies": { "@types/react-dom": "^19.1.9", diff --git a/package.json b/package.json index 19df1ff..38b237d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "virtual-react-json-diff", "type": "module", - "version": "1.0.12", + "version": "1.0.13", "description": "Fast, virtualized React component for visually comparing large JSON objects. Includes search, theming, and minimap.", "author": { "name": "Utku Akyüz" From c38ea844d03b4a524d853d1602093081d881bd54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Utku=20Aky=C3=BCz?= Date: Tue, 16 Sep 2025 14:21:24 +0300 Subject: [PATCH 4/4] chore: demo version downgrade --- demo/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/package.json b/demo/package.json index 965edb9..1c6152d 100644 --- a/demo/package.json +++ b/demo/package.json @@ -14,7 +14,7 @@ "react-ace": "^14.0.1", "react-dom": "^18.0.0", "react-json-editor-ajrm": "^2.5.14", - "virtual-react-json-diff": "^1.0.13" + "virtual-react-json-diff": "^1.0.12" }, "devDependencies": { "@types/react-dom": "^19.1.9",