@git-diff-view/react 0.0.26 → 0.0.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/dist/cjs/index.development.js +419 -287
  2. package/dist/cjs/index.development.js.map +1 -1
  3. package/dist/cjs/index.production.js +419 -287
  4. package/dist/cjs/index.production.js.map +1 -1
  5. package/dist/css/diff-view-pure.css +4 -0
  6. package/dist/css/diff-view.css +4 -0
  7. package/dist/esm/index.mjs +353 -224
  8. package/dist/esm/index.mjs.map +1 -1
  9. package/index.d.ts +296 -16
  10. package/package.json +4 -3
  11. package/src/_base.css +3 -0
  12. package/src/_base_pure.css +2 -0
  13. package/src/_com.css +172 -0
  14. package/src/_theme.css +2 -0
  15. package/src/components/DiffAddWidget.tsx +86 -0
  16. package/src/components/DiffContent.tsx +367 -0
  17. package/src/components/DiffContent_v2.tsx +344 -0
  18. package/src/components/DiffExpand.tsx +25 -0
  19. package/src/components/DiffNoNewLine.tsx +10 -0
  20. package/src/components/DiffSplitContentLineNormal.tsx +164 -0
  21. package/src/components/DiffSplitContentLineWrap.tsx +234 -0
  22. package/src/components/DiffSplitExtendLineNormal.tsx +150 -0
  23. package/src/components/DiffSplitExtendLineWrap.tsx +133 -0
  24. package/src/components/DiffSplitHunkLineNormal.tsx +316 -0
  25. package/src/components/DiffSplitHunkLineWrap.tsx +340 -0
  26. package/src/components/DiffSplitView.tsx +46 -0
  27. package/src/components/DiffSplitViewNormal.tsx +205 -0
  28. package/src/components/DiffSplitViewWrap.tsx +141 -0
  29. package/src/components/DiffSplitWidgetLineNormal.tsx +149 -0
  30. package/src/components/DiffSplitWidgetLineWrap.tsx +127 -0
  31. package/src/components/DiffUnifiedContentLine.tsx +342 -0
  32. package/src/components/DiffUnifiedExtendLine.tsx +103 -0
  33. package/src/components/DiffUnifiedHunkLine.tsx +148 -0
  34. package/src/components/DiffUnifiedView.tsx +159 -0
  35. package/src/components/DiffUnifiedWidgetLine.tsx +104 -0
  36. package/src/components/DiffView.tsx +365 -0
  37. package/src/components/DiffViewContext.ts +11 -0
  38. package/src/components/DiffWidgetContext.ts +17 -0
  39. package/src/components/tools.ts +132 -0
  40. package/src/components/v2/DiffSplitContentLineNormal_v2.tsx +152 -0
  41. package/src/components/v2/DiffSplitContentLineWrap_v2.tsx +259 -0
  42. package/src/components/v2/DiffSplitExtendLineNormal_v2.tsx +146 -0
  43. package/src/components/v2/DiffSplitExtendLineWrap_v2.tsx +123 -0
  44. package/src/components/v2/DiffSplitHunkLineNormal_v2.tsx +302 -0
  45. package/src/components/v2/DiffSplitHunkLineWrap_v2.tsx +326 -0
  46. package/src/components/v2/DiffSplitViewLineNormal_v2.tsx +33 -0
  47. package/src/components/v2/DiffSplitViewLineWrap_v2.tsx +24 -0
  48. package/src/components/v2/DiffSplitViewNormal_v2.tsx +159 -0
  49. package/src/components/v2/DiffSplitViewWrap_v2.tsx +104 -0
  50. package/src/components/v2/DiffSplitView_v2.tsx +47 -0
  51. package/src/components/v2/DiffSplitWidgetLineNormal_v2.tsx +132 -0
  52. package/src/components/v2/DiffSplitWidgetLineWrap_v2.tsx +119 -0
  53. package/src/global.d.ts +12 -0
  54. package/src/hooks/useCallbackRef.ts +18 -0
  55. package/src/hooks/useDomWidth.ts +67 -0
  56. package/src/hooks/useIsMounted.ts +11 -0
  57. package/src/hooks/useSafeLayout.ts +5 -0
  58. package/src/hooks/useSyncHeight.ts +87 -0
  59. package/src/hooks/useTextWidth.ts +27 -0
  60. package/src/hooks/useUnmount.ts +10 -0
  61. package/src/index.ts +3 -0
  62. package/src/tailwind.css +3 -0
  63. package/src/tailwind_pure.css +3 -0
  64. package/styles/diff-view-pure.css +4 -0
  65. package/styles/diff-view.css +4 -0
@@ -0,0 +1,326 @@
1
+ import { composeLen, type DiffFile } from "@git-diff-view/core";
2
+ import {
3
+ diffAsideWidthName,
4
+ hunkLineNumberBGName,
5
+ plainLineNumberColorName,
6
+ hunkContentBGName,
7
+ hunkContentColorName,
8
+ borderColorName,
9
+ } from "@git-diff-view/utils";
10
+ import * as React from "react";
11
+
12
+ import { ExpandUp, ExpandDown, ExpandAll } from "../DiffExpand";
13
+ import { DiffModeEnum } from "../DiffView";
14
+ import { useDiffViewContext } from "../DiffViewContext";
15
+
16
+ const DiffSplitHunkLineGitHub = ({
17
+ index,
18
+ diffFile,
19
+ lineNumber,
20
+ }: {
21
+ index: number;
22
+ diffFile: DiffFile;
23
+ lineNumber: number;
24
+ }) => {
25
+ const currentHunk = diffFile.getSplitHunkLine(index);
26
+
27
+ const expandEnabled = diffFile.getExpandEnabled();
28
+
29
+ const couldExpand = expandEnabled && currentHunk && currentHunk.splitInfo;
30
+
31
+ const isExpandAll =
32
+ currentHunk &&
33
+ currentHunk.splitInfo &&
34
+ currentHunk.splitInfo.endHiddenIndex - currentHunk.splitInfo.startHiddenIndex < composeLen;
35
+
36
+ const isFirstLine = currentHunk && currentHunk.isFirst;
37
+
38
+ const isLastLine = currentHunk && currentHunk.isLast;
39
+
40
+ return (
41
+ <div data-line={`${lineNumber}-hunk`} data-state="hunk" className="diff-line diff-line-hunk flex">
42
+ <div
43
+ className="diff-line-hunk-action flex w-[1%] min-w-[40px] select-none flex-col items-center justify-center p-[1px]"
44
+ style={{
45
+ backgroundColor: `var(${hunkLineNumberBGName})`,
46
+ color: `var(${plainLineNumberColorName})`,
47
+ width: `var(${diffAsideWidthName})`,
48
+ minWidth: `var(${diffAsideWidthName})`,
49
+ maxWidth: `var(${diffAsideWidthName})`,
50
+ }}
51
+ >
52
+ {couldExpand ? (
53
+ isFirstLine ? (
54
+ <button
55
+ className="diff-widget-tooltip flex w-full cursor-pointer items-center justify-center rounded-[2px] py-[6px]"
56
+ title="Expand Up"
57
+ data-title="Expand Up"
58
+ onClick={() => diffFile.onSplitHunkExpand("up", index)}
59
+ >
60
+ <ExpandUp className="fill-current" />
61
+ </button>
62
+ ) : isLastLine ? (
63
+ <button
64
+ className="diff-widget-tooltip flex w-full cursor-pointer items-center justify-center rounded-[2px] py-[6px]"
65
+ title="Expand Down"
66
+ data-title="Expand Down"
67
+ onClick={() => diffFile.onSplitHunkExpand("down", index)}
68
+ >
69
+ <ExpandDown className="fill-current" />
70
+ </button>
71
+ ) : isExpandAll ? (
72
+ <button
73
+ className="diff-widget-tooltip flex w-full cursor-pointer items-center justify-center rounded-[2px] py-[6px]"
74
+ title="Expand All"
75
+ data-title="Expand All"
76
+ onClick={() => diffFile.onSplitHunkExpand("all", index)}
77
+ >
78
+ <ExpandAll className="fill-current" />
79
+ </button>
80
+ ) : (
81
+ <>
82
+ <button
83
+ className="diff-widget-tooltip flex w-full cursor-pointer items-center justify-center rounded-[2px] py-[2px]"
84
+ title="Expand Down"
85
+ data-title="Expand Down"
86
+ onClick={() => diffFile.onSplitHunkExpand("down", index)}
87
+ >
88
+ <ExpandDown className="fill-current" />
89
+ </button>
90
+ <button
91
+ className="diff-widget-tooltip flex w-full cursor-pointer items-center justify-center rounded-[2px] py-[2px]"
92
+ title="Expand Up"
93
+ data-title="Expand Up"
94
+ onClick={() => diffFile.onSplitHunkExpand("up", index)}
95
+ >
96
+ <ExpandUp className="fill-current" />
97
+ </button>
98
+ </>
99
+ )
100
+ ) : (
101
+ <div className="min-h-[28px]">&ensp;</div>
102
+ )}
103
+ </div>
104
+ <div
105
+ className="diff-line-hunk-content flex w-full items-center px-[10px]"
106
+ style={{ backgroundColor: `var(${hunkContentBGName})`, color: `var(${hunkContentColorName})` }}
107
+ >
108
+ {currentHunk.splitInfo?.plainText || currentHunk.text}
109
+ </div>
110
+ </div>
111
+ );
112
+ };
113
+
114
+ const DiffSplitHunkLineGitLab = ({
115
+ index,
116
+ diffFile,
117
+ lineNumber,
118
+ }: {
119
+ index: number;
120
+ diffFile: DiffFile;
121
+ lineNumber: number;
122
+ }) => {
123
+ const currentHunk = diffFile.getSplitHunkLine(index);
124
+
125
+ const expandEnabled = diffFile.getExpandEnabled();
126
+
127
+ const couldExpand = expandEnabled && currentHunk && currentHunk.splitInfo;
128
+
129
+ const isExpandAll =
130
+ currentHunk &&
131
+ currentHunk.splitInfo &&
132
+ currentHunk.splitInfo.endHiddenIndex - currentHunk.splitInfo.startHiddenIndex < composeLen;
133
+
134
+ const isFirstLine = currentHunk && currentHunk.isFirst;
135
+
136
+ const isLastLine = currentHunk && currentHunk.isLast;
137
+
138
+ return (
139
+ <div data-line={`${lineNumber}-hunk`} data-state="hunk" className="diff-line diff-line-hunk flex">
140
+ <div
141
+ className="diff-line-hunk-action flex w-[1%] min-w-[40px] select-none flex-col items-center justify-center p-[1px]"
142
+ style={{
143
+ backgroundColor: `var(${hunkLineNumberBGName})`,
144
+ color: `var(${plainLineNumberColorName})`,
145
+ width: `var(${diffAsideWidthName})`,
146
+ minWidth: `var(${diffAsideWidthName})`,
147
+ maxWidth: `var(${diffAsideWidthName})`,
148
+ }}
149
+ >
150
+ {couldExpand ? (
151
+ isFirstLine ? (
152
+ <button
153
+ className="diff-widget-tooltip flex h-full w-full cursor-pointer items-center justify-center rounded-[2px] py-[6px]"
154
+ title="Expand Up"
155
+ data-title="Expand Up"
156
+ onClick={() => diffFile.onSplitHunkExpand("up", index)}
157
+ >
158
+ <ExpandUp className="fill-current" />
159
+ </button>
160
+ ) : isLastLine ? (
161
+ <button
162
+ className="diff-widget-tooltip flex h-full w-full cursor-pointer items-center justify-center rounded-[2px] py-[6px]"
163
+ title="Expand Down"
164
+ data-title="Expand Down"
165
+ onClick={() => diffFile.onSplitHunkExpand("down", index)}
166
+ >
167
+ <ExpandDown className="fill-current" />
168
+ </button>
169
+ ) : isExpandAll ? (
170
+ <button
171
+ className="diff-widget-tooltip flex h-full w-full cursor-pointer items-center justify-center rounded-[2px] py-[6px]"
172
+ title="Expand All"
173
+ data-title="Expand All"
174
+ onClick={() => diffFile.onSplitHunkExpand("all", index)}
175
+ >
176
+ <ExpandAll className="fill-current" />
177
+ </button>
178
+ ) : (
179
+ <>
180
+ <button
181
+ className="diff-widget-tooltip flex h-[50%] w-full cursor-pointer items-center justify-center rounded-[2px] py-[2px]"
182
+ title="Expand Down"
183
+ data-title="Expand Down"
184
+ onClick={() => diffFile.onSplitHunkExpand("down", index)}
185
+ >
186
+ <ExpandDown className="fill-current" />
187
+ </button>
188
+ <button
189
+ className="diff-widget-tooltip flex h-[50%] w-full cursor-pointer items-center justify-center rounded-[2px] py-[2px]"
190
+ title="Expand Up"
191
+ data-title="Expand Up"
192
+ onClick={() => diffFile.onSplitHunkExpand("up", index)}
193
+ >
194
+ <ExpandUp className="fill-current" />
195
+ </button>
196
+ </>
197
+ )
198
+ ) : (
199
+ <div className="min-h-[28px]">&ensp;</div>
200
+ )}
201
+ </div>
202
+ <div
203
+ className="diff-line-hunk-content flex w-full items-center px-[10px]"
204
+ style={{ backgroundColor: `var(${hunkContentBGName})`, color: `var(${hunkContentColorName})` }}
205
+ >
206
+ {currentHunk.splitInfo?.plainText || currentHunk.text}
207
+ </div>
208
+ <div className="diff-split-line w-[1px] flex-shrink-0" style={{ backgroundColor: `var(${borderColorName})` }} />
209
+ <div
210
+ className="diff-line-hunk-action relative z-[1] flex w-[1%] min-w-[40px] select-none flex-col items-center justify-center p-[1px]"
211
+ style={{
212
+ backgroundColor: `var(${hunkLineNumberBGName})`,
213
+ color: `var(${plainLineNumberColorName})`,
214
+ width: `var(${diffAsideWidthName})`,
215
+ minWidth: `var(${diffAsideWidthName})`,
216
+ maxWidth: `var(${diffAsideWidthName})`,
217
+ }}
218
+ >
219
+ {couldExpand ? (
220
+ isFirstLine ? (
221
+ <button
222
+ className="diff-widget-tooltip flex h-full w-full cursor-pointer items-center justify-center rounded-[2px] py-[6px]"
223
+ title="Expand Up"
224
+ data-title="Expand Up"
225
+ onClick={() => diffFile.onSplitHunkExpand("up", index)}
226
+ >
227
+ <ExpandUp className="fill-current" />
228
+ </button>
229
+ ) : isLastLine ? (
230
+ <button
231
+ className="diff-widget-tooltip flex h-full w-full cursor-pointer items-center justify-center rounded-[2px] py-[6px]"
232
+ title="Expand Down"
233
+ data-title="Expand Down"
234
+ onClick={() => diffFile.onSplitHunkExpand("down", index)}
235
+ >
236
+ <ExpandDown className="fill-current" />
237
+ </button>
238
+ ) : isExpandAll ? (
239
+ <button
240
+ className="diff-widget-tooltip flex h-full w-full cursor-pointer items-center justify-center rounded-[2px] py-[6px]"
241
+ title="Expand All"
242
+ data-title="Expand All"
243
+ onClick={() => diffFile.onSplitHunkExpand("all", index)}
244
+ >
245
+ <ExpandAll className="fill-current" />
246
+ </button>
247
+ ) : (
248
+ <>
249
+ <button
250
+ className="diff-widget-tooltip flex h-[50%] w-full cursor-pointer items-center justify-center rounded-[2px] py-[2px]"
251
+ title="Expand Down"
252
+ data-title="Expand Down"
253
+ onClick={() => diffFile.onSplitHunkExpand("down", index)}
254
+ >
255
+ <ExpandDown className="fill-current" />
256
+ </button>
257
+ <button
258
+ className="diff-widget-tooltip flex h-[50%] w-full cursor-pointer items-center justify-center rounded-[2px] py-[2px]"
259
+ title="Expand Up"
260
+ data-title="Expand Up"
261
+ onClick={() => diffFile.onSplitHunkExpand("up", index)}
262
+ >
263
+ <ExpandUp className="fill-current" />
264
+ </button>
265
+ </>
266
+ )
267
+ ) : (
268
+ <div className="min-h-[28px]">&ensp;</div>
269
+ )}
270
+ </div>
271
+ <div
272
+ className="diff-line-hunk-content relative flex w-full items-center px-[10px]"
273
+ style={{ backgroundColor: `var(${hunkContentBGName})`, color: `var(${hunkContentColorName})` }}
274
+ >
275
+ {currentHunk.splitInfo?.plainText || currentHunk.text}
276
+ </div>
277
+ </div>
278
+ );
279
+ };
280
+
281
+ const InternalDiffSplitHunkLine = ({
282
+ index,
283
+ diffFile,
284
+ lineNumber,
285
+ }: {
286
+ index: number;
287
+ diffFile: DiffFile;
288
+ lineNumber: number;
289
+ }) => {
290
+ const { useDiffContext } = useDiffViewContext();
291
+
292
+ const diffViewMode = useDiffContext.useShallowStableSelector((s) => s.mode);
293
+
294
+ if (
295
+ diffViewMode === DiffModeEnum.SplitGitHub ||
296
+ diffViewMode === DiffModeEnum.Split ||
297
+ diffViewMode === DiffModeEnum.Unified
298
+ ) {
299
+ return <DiffSplitHunkLineGitHub index={index} diffFile={diffFile} lineNumber={lineNumber} />;
300
+ } else {
301
+ return <DiffSplitHunkLineGitLab index={index} diffFile={diffFile} lineNumber={lineNumber} />;
302
+ }
303
+ };
304
+
305
+ export const DiffSplitHunkLine = ({
306
+ index,
307
+ diffFile,
308
+ lineNumber,
309
+ }: {
310
+ index: number;
311
+ diffFile: DiffFile;
312
+ lineNumber: number;
313
+ }) => {
314
+ const currentHunk = diffFile.getSplitHunkLine(index);
315
+
316
+ const currentIsShow =
317
+ currentHunk &&
318
+ currentHunk.splitInfo &&
319
+ currentHunk.splitInfo.startHiddenIndex < currentHunk.splitInfo.endHiddenIndex;
320
+
321
+ const currentIsPureHunk = currentHunk && diffFile._getIsPureDiffRender() && !currentHunk.splitInfo;
322
+
323
+ if (!currentIsShow && !currentIsPureHunk) return null;
324
+
325
+ return <InternalDiffSplitHunkLine index={index} diffFile={diffFile} lineNumber={lineNumber} />;
326
+ };
@@ -0,0 +1,33 @@
1
+ import { DiffFileLineType } from "@git-diff-view/core";
2
+ import * as React from "react";
3
+
4
+ import { DiffSplitContentLine } from "./DiffSplitContentLineNormal_v2";
5
+ import { DiffSplitExtendLine } from "./DiffSplitExtendLineNormal_v2";
6
+ import { DiffSplitHunkLine } from "./DiffSplitHunkLineNormal_v2";
7
+ import { DiffSplitWidgetLine } from "./DiffSplitWidgetLineNormal_v2";
8
+
9
+ import type { SplitSide } from "../DiffView";
10
+ import type { DiffFile, DiffSplitLineItem } from "@git-diff-view/core";
11
+
12
+ export const DiffSplitViewLine = ({
13
+ line,
14
+ side,
15
+ diffFile,
16
+ }: {
17
+ line: DiffSplitLineItem;
18
+ side: SplitSide;
19
+ diffFile: DiffFile;
20
+ }) => {
21
+ switch (line.type) {
22
+ case DiffFileLineType.hunk:
23
+ return <DiffSplitHunkLine side={side} index={line.index} lineNumber={line.lineNumber} diffFile={diffFile} />;
24
+ case DiffFileLineType.content:
25
+ return <DiffSplitContentLine side={side} index={line.index} lineNumber={line.lineNumber} diffFile={diffFile} />;
26
+ case DiffFileLineType.widget:
27
+ return <DiffSplitWidgetLine side={side} index={line.index} lineNumber={line.lineNumber} diffFile={diffFile} />;
28
+ case DiffFileLineType.extend:
29
+ return <DiffSplitExtendLine side={side} index={line.index} lineNumber={line.lineNumber} diffFile={diffFile} />;
30
+ default:
31
+ return null;
32
+ }
33
+ };
@@ -0,0 +1,24 @@
1
+ import { DiffFileLineType } from "@git-diff-view/core";
2
+ import * as React from "react";
3
+
4
+ import { DiffSplitContentLine } from "./DiffSplitContentLineWrap_v2";
5
+ import { DiffSplitExtendLine } from "./DiffSplitExtendLineWrap_v2";
6
+ import { DiffSplitHunkLine } from "./DiffSplitHunkLineWrap_v2";
7
+ import { DiffSplitWidgetLine } from "./DiffSplitWidgetLineWrap_v2";
8
+
9
+ import type { DiffFile, DiffSplitLineItem } from "@git-diff-view/core";
10
+
11
+ export const DiffSplitViewLine = ({ line, diffFile }: { line: DiffSplitLineItem; diffFile: DiffFile }) => {
12
+ switch (line.type) {
13
+ case DiffFileLineType.hunk:
14
+ return <DiffSplitHunkLine index={line.index} lineNumber={line.lineNumber} diffFile={diffFile} />;
15
+ case DiffFileLineType.content:
16
+ return <DiffSplitContentLine index={line.index} lineNumber={line.lineNumber} diffFile={diffFile} />;
17
+ case DiffFileLineType.widget:
18
+ return <DiffSplitWidgetLine index={line.index} lineNumber={line.lineNumber} diffFile={diffFile} />;
19
+ case DiffFileLineType.extend:
20
+ return <DiffSplitExtendLine index={line.index} lineNumber={line.lineNumber} diffFile={diffFile} />;
21
+ default:
22
+ return null;
23
+ }
24
+ };
@@ -0,0 +1,159 @@
1
+ /* eslint-disable @typescript-eslint/ban-ts-comment */
2
+ import { DiffFileLineType, getSplitLines, type DiffFile } from "@git-diff-view/core";
3
+ import {
4
+ removeAllSelection,
5
+ syncScroll,
6
+ diffFontSizeName,
7
+ diffAsideWidthName,
8
+ borderColorName,
9
+ } from "@git-diff-view/utils";
10
+ import { memo, useEffect, useRef } from "react";
11
+ import * as React from "react";
12
+ import { useSyncExternalStore } from "use-sync-external-store/shim/index.js";
13
+
14
+ import { useTextWidth } from "../../hooks/useTextWidth";
15
+ import { SplitSide } from "../DiffView";
16
+ import { useDiffViewContext } from "../DiffViewContext";
17
+
18
+ import { DiffSplitViewLine } from "./DiffSplitViewLineNormal_v2";
19
+
20
+ import type { MouseEventHandler } from "react";
21
+
22
+ export const DiffSplitViewTable = ({
23
+ side,
24
+ diffFile,
25
+ onMouseDown,
26
+ }: {
27
+ side: SplitSide;
28
+ diffFile: DiffFile;
29
+ onMouseDown?: MouseEventHandler<HTMLDivElement>;
30
+ }) => {
31
+ const className = side === SplitSide.new ? "new-diff-table" : "old-diff-table";
32
+
33
+ const lines = getSplitLines(diffFile);
34
+
35
+ return (
36
+ <div className={className + " w-max min-w-full"} data-mode={SplitSide[side]}>
37
+ <div className="diff-table-body leading-[1.6]" onMouseDownCapture={onMouseDown}>
38
+ {lines.map((line) => (
39
+ <DiffSplitViewLine
40
+ key={line.index + "-" + DiffFileLineType[line.type]}
41
+ side={side}
42
+ line={line}
43
+ diffFile={diffFile}
44
+ />
45
+ ))}
46
+ </div>
47
+ </div>
48
+ );
49
+ };
50
+
51
+ export const DiffSplitViewNormal = memo(({ diffFile }: { diffFile: DiffFile }) => {
52
+ const ref1 = useRef<HTMLDivElement>(null);
53
+
54
+ const ref2 = useRef<HTMLDivElement>(null);
55
+
56
+ const ref = useRef<HTMLStyleElement>();
57
+
58
+ const splitLineLength = Math.max(diffFile.splitLineLength, diffFile.fileLineLength);
59
+
60
+ const { useDiffContext } = useDiffViewContext();
61
+
62
+ const fontSize = useDiffContext.useShallowStableSelector((s) => s.fontSize);
63
+
64
+ useSyncExternalStore(diffFile.subscribe, diffFile.getUpdateCount, diffFile.getUpdateCount);
65
+
66
+ useEffect(() => {
67
+ const left = ref1.current;
68
+ const right = ref2.current;
69
+ if (!left || !right) return;
70
+ return syncScroll(left, right);
71
+ }, []);
72
+
73
+ const font = React.useMemo(
74
+ () => ({ fontSize: fontSize + "px", fontFamily: "Menlo, Consolas, monospace" }),
75
+ [fontSize]
76
+ );
77
+
78
+ const _width = useTextWidth({
79
+ text: splitLineLength.toString(),
80
+ font,
81
+ });
82
+
83
+ const width = Math.max(40, _width + 25);
84
+
85
+ const setStyle = (side: SplitSide) => {
86
+ if (!ref.current) return;
87
+ if (!side) {
88
+ ref.current.textContent = "";
89
+ } else {
90
+ const id = `diff-root${diffFile.getId()}`;
91
+ ref.current.textContent = `#${id} [data-state="extend"] {user-select: none} \n#${id} [data-state="hunk"] {user-select: none} \n#${id} [data-state="widget"] {user-select: none}`;
92
+ }
93
+ };
94
+
95
+ const onMouseDown: MouseEventHandler<HTMLTableSectionElement> = (e) => {
96
+ let ele = e.target;
97
+
98
+ // need remove all the selection
99
+ if (ele && ele instanceof HTMLElement && ele.nodeName === "BUTTON") {
100
+ removeAllSelection();
101
+ return;
102
+ }
103
+
104
+ while (ele && ele instanceof HTMLElement) {
105
+ const state = ele.getAttribute("data-state");
106
+ const side = ele.getAttribute("data-side");
107
+ if (side) {
108
+ setStyle(SplitSide[side]);
109
+ removeAllSelection();
110
+ }
111
+ if (state) {
112
+ if (state === "extend" || state === "hunk" || state === "widget") {
113
+ setStyle(undefined);
114
+ removeAllSelection();
115
+ return;
116
+ } else {
117
+ return;
118
+ }
119
+ }
120
+
121
+ ele = ele.parentElement;
122
+ }
123
+ };
124
+
125
+ return (
126
+ <div className="split-diff-view split-diff-view-normal flex w-full basis-[50%]">
127
+ <style data-select-style ref={ref} />
128
+ <div
129
+ className="old-diff-table-wrapper diff-table-scroll-container w-full overflow-x-auto overflow-y-hidden"
130
+ ref={ref1}
131
+ style={{
132
+ // @ts-ignore
133
+ [diffAsideWidthName]: `${Math.round(width)}px`,
134
+ overscrollBehaviorX: "none",
135
+ fontFamily: "Menlo, Consolas, monospace",
136
+ fontSize: `var(${diffFontSizeName})`,
137
+ }}
138
+ >
139
+ <DiffSplitViewTable side={SplitSide.old} diffFile={diffFile} onMouseDown={onMouseDown} />
140
+ </div>
141
+ <div className="diff-split-line w-[1.5px]" style={{ backgroundColor: `var(${borderColorName})` }} />
142
+ <div
143
+ className="new-diff-table-wrapper diff-table-scroll-container w-full overflow-x-auto overflow-y-hidden"
144
+ ref={ref2}
145
+ style={{
146
+ // @ts-ignore
147
+ [diffAsideWidthName]: `${Math.round(width)}px`,
148
+ overscrollBehaviorX: "none",
149
+ fontFamily: "Menlo, Consolas, monospace",
150
+ fontSize: `var(${diffFontSizeName})`,
151
+ }}
152
+ >
153
+ <DiffSplitViewTable side={SplitSide.new} diffFile={diffFile} onMouseDown={onMouseDown} />
154
+ </div>
155
+ </div>
156
+ );
157
+ });
158
+
159
+ DiffSplitViewNormal.displayName = "DiffSplitViewNormal";
@@ -0,0 +1,104 @@
1
+ /* eslint-disable @typescript-eslint/ban-ts-comment */
2
+ import { type DiffFile, getSplitLines } from "@git-diff-view/core";
3
+ import { removeAllSelection, diffFontSizeName, diffAsideWidthName } from "@git-diff-view/utils";
4
+ import { memo, useMemo, useRef } from "react";
5
+ import * as React from "react";
6
+ // SEE https://github.com/facebook/react/pull/25231
7
+ import { useSyncExternalStore } from "use-sync-external-store/shim/index.js";
8
+
9
+ import { useTextWidth } from "../../hooks/useTextWidth";
10
+ import { SplitSide } from "../DiffView";
11
+ import { useDiffViewContext } from "../DiffViewContext";
12
+
13
+ import { DiffSplitViewLine } from "./DiffSplitViewLineWrap_v2";
14
+
15
+ import type { MouseEventHandler } from "react";
16
+
17
+ export const DiffSplitViewWrap = memo(({ diffFile }: { diffFile: DiffFile }) => {
18
+ const splitLineLength = Math.max(diffFile.splitLineLength, diffFile.fileLineLength);
19
+
20
+ const { useDiffContext } = useDiffViewContext();
21
+
22
+ const ref = useRef<HTMLStyleElement>(null);
23
+
24
+ const fontSize = useDiffContext.useShallowStableSelector((s) => s.fontSize);
25
+
26
+ useSyncExternalStore(diffFile.subscribe, diffFile.getUpdateCount, diffFile.getUpdateCount);
27
+
28
+ const font = useMemo(() => ({ fontSize: fontSize + "px", fontFamily: "Menlo, Consolas, monospace" }), [fontSize]);
29
+
30
+ const _width = useTextWidth({
31
+ text: splitLineLength.toString(),
32
+ font,
33
+ });
34
+
35
+ const width = Math.max(40, _width + 25);
36
+
37
+ const lines = getSplitLines(diffFile);
38
+
39
+ const setStyle = (side: SplitSide) => {
40
+ if (!ref.current) return;
41
+ if (!side) {
42
+ ref.current.textContent = "";
43
+ } else {
44
+ const id = `diff-root${diffFile.getId()}`;
45
+ const targetSide = side === SplitSide.old ? SplitSide.new : SplitSide.old;
46
+ ref.current.textContent = `#${id} [data-side="${SplitSide[targetSide]}"] {user-select: none} \n#${id} [data-state="extend"] {user-select: none} \n#${id} [data-state="hunk"] {user-select: none} \n#${id} [data-state="widget"] {user-select: none}`;
47
+ }
48
+ };
49
+
50
+ const onMouseDown: MouseEventHandler<HTMLTableSectionElement> = (e) => {
51
+ let ele = e.target;
52
+
53
+ // need remove all the selection
54
+ if (ele && ele instanceof HTMLElement && ele.nodeName === "BUTTON") {
55
+ removeAllSelection();
56
+ return;
57
+ }
58
+
59
+ while (ele && ele instanceof HTMLElement) {
60
+ const state = ele.getAttribute("data-state");
61
+ const side = ele.getAttribute("data-side");
62
+ if (side) {
63
+ setStyle(SplitSide[side]);
64
+ removeAllSelection();
65
+ }
66
+ if (state) {
67
+ if (state === "extend" || state === "hunk" || state === "widget") {
68
+ setStyle(undefined);
69
+ removeAllSelection();
70
+ return;
71
+ } else {
72
+ return;
73
+ }
74
+ }
75
+
76
+ ele = ele.parentElement;
77
+ }
78
+ };
79
+
80
+ return (
81
+ <div className="split-diff-view split-diff-view-warp w-full">
82
+ <div
83
+ className="diff-table-wrapper w-full"
84
+ style={{
85
+ // @ts-ignore
86
+ [diffAsideWidthName]: `${Math.round(width)}px`,
87
+ fontFamily: "Menlo, Consolas, monospace",
88
+ fontSize: `var(${diffFontSizeName})`,
89
+ }}
90
+ >
91
+ <style data-select-style ref={ref} />
92
+ <div className="diff-table w-full table-fixed border-collapse">
93
+ <div className="diff-table-body leading-[1.6]" onMouseDownCapture={onMouseDown}>
94
+ {lines.map((line, index) => (
95
+ <DiffSplitViewLine key={line.index + index} line={line} diffFile={diffFile} />
96
+ ))}
97
+ </div>
98
+ </div>
99
+ </div>
100
+ </div>
101
+ );
102
+ });
103
+
104
+ DiffSplitViewWrap.displayName = "DiffSplitViewWrap";
@@ -0,0 +1,47 @@
1
+ import { memo, useEffect, useMemo, useRef } from "react";
2
+ import * as React from "react";
3
+
4
+ import { useDiffViewContext } from "../DiffViewContext";
5
+ import { DiffWidgetContext } from "../DiffWidgetContext";
6
+ import { createDiffWidgetStore } from "../tools";
7
+
8
+ import { DiffSplitViewNormal } from "./DiffSplitViewNormal_v2";
9
+ import { DiffSplitViewWrap } from "./DiffSplitViewWrap_v2";
10
+
11
+ import type { DiffFile } from "@git-diff-view/core";
12
+
13
+ export const DiffSplitView = memo(({ diffFile }: { diffFile: DiffFile }) => {
14
+ const { useDiffContext } = useDiffViewContext();
15
+
16
+ const useDiffContextRef = useRef(useDiffContext);
17
+
18
+ useDiffContextRef.current = useDiffContext;
19
+
20
+ const { enableWrap, onCreateUseWidgetHook } = useDiffContext.useShallowStableSelector((s) => ({
21
+ enableWrap: s.enableWrap,
22
+ onCreateUseWidgetHook: s.onCreateUseWidgetHook,
23
+ }));
24
+
25
+ // performance optimization
26
+ const useWidget = useMemo(() => createDiffWidgetStore(useDiffContextRef), []);
27
+
28
+ const contextValue = useMemo(() => ({ useWidget }), [useWidget]);
29
+
30
+ useEffect(() => {
31
+ const { setWidget } = useWidget.getReadonlyState();
32
+
33
+ setWidget({});
34
+ }, [diffFile, useWidget]);
35
+
36
+ useEffect(() => {
37
+ onCreateUseWidgetHook?.(useWidget);
38
+ }, [useWidget, onCreateUseWidgetHook]);
39
+
40
+ return (
41
+ <DiffWidgetContext.Provider value={contextValue}>
42
+ {enableWrap ? <DiffSplitViewWrap diffFile={diffFile} /> : <DiffSplitViewNormal diffFile={diffFile} />}
43
+ </DiffWidgetContext.Provider>
44
+ );
45
+ });
46
+
47
+ DiffSplitView.displayName = "DiffSplitView";