@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,132 @@
1
+ import { createStore, ref } from "reactivity-store";
2
+
3
+ import type { DiffModeEnum, DiffViewProps, SplitSide } from "./DiffView";
4
+ import type { RefObject } from "react";
5
+
6
+ export const createDiffConfigStore = (props: DiffViewProps<any> & { isMounted: boolean }, diffFileId: string) => {
7
+ return createStore(() => {
8
+ const id = ref(diffFileId);
9
+
10
+ const setId = (_id: string) => (id.value = _id);
11
+
12
+ const mode = ref(props.diffViewMode);
13
+
14
+ const setMode = (_mode: DiffModeEnum) => (mode.value = _mode);
15
+
16
+ const mounted = ref(props.isMounted);
17
+
18
+ const setMounted = (_mounted: boolean) => (mounted.value = _mounted);
19
+
20
+ const enableWrap = ref(props.diffViewWrap);
21
+
22
+ const setEnableWrap = (_enableWrap: boolean) => (enableWrap.value = _enableWrap);
23
+
24
+ const enableAddWidget = ref(props.diffViewAddWidget);
25
+
26
+ const setEnableAddWidget = (_enableAddWidget: boolean) => (enableAddWidget.value = _enableAddWidget);
27
+
28
+ const enableHighlight = ref(props.diffViewHighlight);
29
+
30
+ const setEnableHighlight = (_enableHighlight: boolean) => (enableHighlight.value = _enableHighlight);
31
+
32
+ const fontSize = ref(props.diffViewFontSize);
33
+
34
+ const setFontSize = (_fontSize: number) => (fontSize.value = _fontSize);
35
+
36
+ const extendData = ref({
37
+ oldFile: { ...props.extendData?.oldFile },
38
+ newFile: { ...props.extendData?.newFile },
39
+ });
40
+
41
+ const setExtendData = (_extendData: DiffViewProps<any>["extendData"]) => {
42
+ const existOldKeys = Object.keys(extendData.value.oldFile || {});
43
+ const inComingOldKeys = Object.keys(_extendData.oldFile || {});
44
+ for (const key of existOldKeys) {
45
+ if (!inComingOldKeys.includes(key)) {
46
+ delete extendData.value.oldFile[key];
47
+ }
48
+ }
49
+ for (const key of inComingOldKeys) {
50
+ extendData.value.oldFile[key] = _extendData.oldFile[key];
51
+ }
52
+ const existNewKeys = Object.keys(extendData.value.newFile || {});
53
+ const inComingNewKeys = Object.keys(_extendData.newFile || {});
54
+ for (const key of existNewKeys) {
55
+ if (!inComingNewKeys.includes(key)) {
56
+ delete extendData.value.newFile[key];
57
+ }
58
+ }
59
+ for (const key of inComingNewKeys) {
60
+ extendData.value.newFile[key] = _extendData.newFile[key];
61
+ }
62
+ };
63
+
64
+ const renderWidgetLine = ref(props.renderWidgetLine);
65
+
66
+ const setRenderWidgetLine = (_renderWidgetLine: typeof renderWidgetLine.value) =>
67
+ (renderWidgetLine.value = _renderWidgetLine);
68
+
69
+ const renderExtendLine = ref(props.renderExtendLine);
70
+
71
+ const setRenderExtendLine = (_renderExtendLine: typeof renderExtendLine.value) =>
72
+ (renderExtendLine.value = _renderExtendLine);
73
+
74
+ const onCreateUseWidgetHook = ref(props.onCreateUseWidgetHook);
75
+
76
+ const setOnCreateUseWidgetHook = (_onCreateUseWidgetHook: typeof onCreateUseWidgetHook.value) =>
77
+ (onCreateUseWidgetHook.value = _onCreateUseWidgetHook);
78
+
79
+ // 避免无意义的订阅
80
+ const onAddWidgetClick = { current: props.onAddWidgetClick };
81
+
82
+ const setOnAddWidgetClick = (_onAddWidgetClick: typeof onAddWidgetClick) =>
83
+ (onAddWidgetClick.current = _onAddWidgetClick.current);
84
+
85
+ return {
86
+ id,
87
+ setId,
88
+ mode,
89
+ setMode,
90
+ mounted,
91
+ setMounted,
92
+ enableWrap,
93
+ setEnableWrap,
94
+ enableAddWidget,
95
+ setEnableAddWidget,
96
+ enableHighlight,
97
+ setEnableHighlight,
98
+ fontSize,
99
+ setFontSize,
100
+ extendData,
101
+ setExtendData,
102
+ renderWidgetLine,
103
+ setRenderWidgetLine,
104
+ renderExtendLine,
105
+ setRenderExtendLine,
106
+ onAddWidgetClick,
107
+ setOnAddWidgetClick,
108
+ onCreateUseWidgetHook,
109
+ setOnCreateUseWidgetHook,
110
+ };
111
+ });
112
+ };
113
+
114
+ export const createDiffWidgetStore = (useDiffContextRef: RefObject<ReturnType<typeof createDiffConfigStore>>) => {
115
+ return createStore(() => {
116
+ const widgetSide = ref<SplitSide>(undefined);
117
+
118
+ const widgetLineNumber = ref<number>(undefined);
119
+
120
+ const setWidget = ({ side, lineNumber }: { side?: SplitSide; lineNumber?: number }) => {
121
+ const { renderWidgetLine } = useDiffContextRef.current?.getReadonlyState?.() || {};
122
+
123
+ if (typeof renderWidgetLine !== "function") return;
124
+
125
+ widgetSide.value = side;
126
+
127
+ widgetLineNumber.value = lineNumber;
128
+ };
129
+
130
+ return { widgetSide, widgetLineNumber, setWidget };
131
+ });
132
+ };
@@ -0,0 +1,152 @@
1
+ import { DiffLineType, type DiffFile, checkDiffLineIncludeChange } from "@git-diff-view/core";
2
+ import {
3
+ getContentBG,
4
+ getLineNumberBG,
5
+ plainLineNumberColorName,
6
+ emptyBGName,
7
+ diffAsideWidthName,
8
+ expandLineNumberColorName,
9
+ } from "@git-diff-view/utils";
10
+ import * as React from "react";
11
+
12
+ import { DiffSplitAddWidget } from "../DiffAddWidget";
13
+ import { DiffContent } from "../DiffContent";
14
+ import { SplitSide } from "../DiffView";
15
+ import { useDiffViewContext } from "../DiffViewContext";
16
+ import { useDiffWidgetContext } from "../DiffWidgetContext";
17
+
18
+ const InternalDiffSplitLine = ({
19
+ index,
20
+ diffFile,
21
+ lineNumber,
22
+ side,
23
+ }: {
24
+ index: number;
25
+ side: SplitSide;
26
+ diffFile: DiffFile;
27
+ lineNumber: number;
28
+ }) => {
29
+ const getCurrentSyntaxLine = side === SplitSide.old ? diffFile.getOldSyntaxLine : diffFile.getNewSyntaxLine;
30
+
31
+ const getCurrentPlainLine = side === SplitSide.old ? diffFile.getOldPlainLine : diffFile.getNewPlainLine;
32
+
33
+ const oldLine = diffFile.getSplitLeftLine(index);
34
+
35
+ const newLine = diffFile.getSplitRightLine(index);
36
+
37
+ const currentLine = side === SplitSide.old ? oldLine : newLine;
38
+
39
+ const hasDiff = !!currentLine?.diff;
40
+
41
+ const hasContent = !!currentLine.lineNumber;
42
+
43
+ const hasChange = checkDiffLineIncludeChange(currentLine?.diff);
44
+
45
+ const isAdded = currentLine?.diff?.type === DiffLineType.Add;
46
+
47
+ const isDelete = currentLine?.diff?.type === DiffLineType.Delete;
48
+
49
+ const { useDiffContext } = useDiffViewContext();
50
+
51
+ const { enableHighlight, enableAddWidget, onAddWidgetClick } = useDiffContext.useShallowStableSelector((s) => ({
52
+ enableHighlight: s.enableHighlight,
53
+ enableAddWidget: s.enableAddWidget,
54
+ onAddWidgetClick: s.onAddWidgetClick,
55
+ }));
56
+
57
+ const { useWidget } = useDiffWidgetContext();
58
+
59
+ const setWidget = useWidget.getReadonlyState().setWidget;
60
+
61
+ const contentBG = getContentBG(isAdded, isDelete, hasDiff);
62
+
63
+ const lineNumberBG = getLineNumberBG(isAdded, isDelete, hasDiff);
64
+
65
+ const plainLine = getCurrentPlainLine(currentLine.lineNumber);
66
+
67
+ const syntaxLine = getCurrentSyntaxLine(currentLine.lineNumber);
68
+
69
+ return (
70
+ <div
71
+ data-line={lineNumber}
72
+ data-state={hasDiff || !hasContent ? "diff" : "plain"}
73
+ data-side={SplitSide[side]}
74
+ className={"diff-line flex" + (hasContent ? " group" : "")}
75
+ >
76
+ {hasContent ? (
77
+ <>
78
+ <div
79
+ className={`diff-line-${SplitSide[side]}-num sticky left-0 flex w-[1%] min-w-[40px] select-none items-center px-[10px] text-right`}
80
+ style={{
81
+ backgroundColor: lineNumberBG,
82
+ color: `var(${hasDiff ? plainLineNumberColorName : expandLineNumberColorName})`,
83
+ width: `var(${diffAsideWidthName})`,
84
+ minWidth: `var(${diffAsideWidthName})`,
85
+ maxWidth: `var(${diffAsideWidthName})`,
86
+ }}
87
+ >
88
+ {hasDiff && enableAddWidget && (
89
+ <DiffSplitAddWidget
90
+ index={index}
91
+ lineNumber={currentLine.lineNumber}
92
+ side={side}
93
+ diffFile={diffFile}
94
+ onWidgetClick={(...props) => onAddWidgetClick.current?.(...props)}
95
+ className="absolute left-[100%] top-[50%] z-[1] translate-x-[-50%] translate-y-[-50%]"
96
+ onOpenAddWidget={(lineNumber, side) => setWidget({ lineNumber: lineNumber, side: side })}
97
+ />
98
+ )}
99
+ <span
100
+ className="w-full"
101
+ data-line-num={currentLine.lineNumber}
102
+ style={{ opacity: hasChange ? undefined : 0.5 }}
103
+ >
104
+ {currentLine.lineNumber}
105
+ </span>
106
+ </div>
107
+ <div
108
+ className={`diff-line-${SplitSide[side]}-content flex w-full items-center pr-[10px]`}
109
+ style={{ backgroundColor: contentBG }}
110
+ >
111
+ <DiffContent
112
+ enableWrap={false}
113
+ diffFile={diffFile}
114
+ rawLine={currentLine.value!}
115
+ diffLine={currentLine.diff}
116
+ plainLine={plainLine}
117
+ syntaxLine={syntaxLine}
118
+ enableHighlight={enableHighlight}
119
+ />
120
+ </div>
121
+ </>
122
+ ) : (
123
+ <div
124
+ className={`diff-line-${SplitSide[side]}-placeholder w-full select-none`}
125
+ style={{ backgroundColor: `var(${emptyBGName})` }}
126
+ >
127
+ &ensp;
128
+ </div>
129
+ )}
130
+ </div>
131
+ );
132
+ };
133
+
134
+ export const DiffSplitContentLine = ({
135
+ index,
136
+ diffFile,
137
+ lineNumber,
138
+ side,
139
+ }: {
140
+ index: number;
141
+ side: SplitSide;
142
+ diffFile: DiffFile;
143
+ lineNumber: number;
144
+ }) => {
145
+ const getCurrentLine = side === SplitSide.old ? diffFile.getSplitLeftLine : diffFile.getSplitRightLine;
146
+
147
+ const currentLine = getCurrentLine(index);
148
+
149
+ if (currentLine?.isHidden) return null;
150
+
151
+ return <InternalDiffSplitLine index={index} diffFile={diffFile} lineNumber={lineNumber} side={side} />;
152
+ };
@@ -0,0 +1,259 @@
1
+ import { DiffLineType, type DiffFile, checkDiffLineIncludeChange } from "@git-diff-view/core";
2
+ import {
3
+ getContentBG,
4
+ getLineNumberBG,
5
+ plainLineNumberColorName,
6
+ emptyBGName,
7
+ borderColorName,
8
+ diffAsideWidthName,
9
+ expandLineNumberColorName,
10
+ } from "@git-diff-view/utils";
11
+ import * as React from "react";
12
+
13
+ import { DiffSplitAddWidget } from "../DiffAddWidget";
14
+ import { DiffContent } from "../DiffContent";
15
+ import { SplitSide } from "../DiffView";
16
+ import { useDiffViewContext } from "../DiffViewContext";
17
+ import { useDiffWidgetContext } from "../DiffWidgetContext";
18
+
19
+ const InternalDiffSplitLine = ({
20
+ index,
21
+ diffFile,
22
+ lineNumber,
23
+ }: {
24
+ index: number;
25
+ diffFile: DiffFile;
26
+ lineNumber: number;
27
+ }) => {
28
+ const oldLine = diffFile.getSplitLeftLine(index);
29
+
30
+ const newLine = diffFile.getSplitRightLine(index);
31
+
32
+ const oldSyntaxLine = diffFile.getOldSyntaxLine(oldLine?.lineNumber);
33
+
34
+ const newSyntaxLine = diffFile.getNewSyntaxLine(newLine?.lineNumber);
35
+
36
+ const oldPlainLine = diffFile.getOldPlainLine(oldLine.lineNumber);
37
+
38
+ const newPlainLine = diffFile.getNewPlainLine(newLine.lineNumber);
39
+
40
+ const hasDiff = !!oldLine?.diff || !!newLine?.diff;
41
+
42
+ const hasChange = checkDiffLineIncludeChange(oldLine?.diff) || checkDiffLineIncludeChange(newLine?.diff);
43
+
44
+ const oldLineIsDelete = oldLine?.diff?.type === DiffLineType.Delete;
45
+
46
+ const newLineIsAdded = newLine?.diff?.type === DiffLineType.Add;
47
+
48
+ const { useDiffContext } = useDiffViewContext();
49
+
50
+ const { enableHighlight, enableAddWidget, onAddWidgetClick } = useDiffContext.useShallowStableSelector((s) => ({
51
+ enableHighlight: s.enableHighlight,
52
+ enableAddWidget: s.enableAddWidget,
53
+ onAddWidgetClick: s.onAddWidgetClick,
54
+ }));
55
+
56
+ const { useWidget } = useDiffWidgetContext();
57
+
58
+ const setWidget = useWidget.getReadonlyState().setWidget;
59
+
60
+ const hasOldLine = !!oldLine.lineNumber;
61
+
62
+ const hasNewLine = !!newLine.lineNumber;
63
+
64
+ const oldLineContentBG = getContentBG(false, oldLineIsDelete, hasDiff);
65
+
66
+ const oldLineNumberBG = getLineNumberBG(false, oldLineIsDelete, hasDiff);
67
+
68
+ const newLineContentBG = getContentBG(newLineIsAdded, false, hasDiff);
69
+
70
+ const newLineNumberBG = getLineNumberBG(newLineIsAdded, false, hasDiff);
71
+
72
+ return (
73
+ <div data-line={lineNumber} data-state={hasDiff ? "diff" : "plain"} className="diff-line flex">
74
+ {hasOldLine ? (
75
+ <>
76
+ <div
77
+ className="diff-line-old-num group relative flex w-[1%] min-w-[40px] select-none items-start px-[10px] text-right"
78
+ data-side={SplitSide[SplitSide.old]}
79
+ style={{
80
+ backgroundColor: oldLineNumberBG,
81
+ color: `var(${hasDiff ? plainLineNumberColorName : expandLineNumberColorName})`,
82
+ width: `var(${diffAsideWidthName})`,
83
+ minWidth: `var(${diffAsideWidthName})`,
84
+ maxWidth: `var(${diffAsideWidthName})`,
85
+ }}
86
+ >
87
+ {hasDiff && enableAddWidget && (
88
+ <DiffSplitAddWidget
89
+ index={index}
90
+ lineNumber={oldLine.lineNumber}
91
+ side={SplitSide.old}
92
+ diffFile={diffFile}
93
+ onWidgetClick={(...props) => onAddWidgetClick.current?.(...props)}
94
+ className="absolute left-[100%] z-[1] translate-x-[-50%]"
95
+ onOpenAddWidget={(lineNumber, side) => setWidget({ lineNumber: lineNumber, side: side })}
96
+ />
97
+ )}
98
+ <span
99
+ className="w-full"
100
+ data-line-num={oldLine.lineNumber}
101
+ style={{ opacity: hasChange ? undefined : 0.5 }}
102
+ >
103
+ {oldLine.lineNumber}
104
+ </span>
105
+ </div>
106
+ <div
107
+ className="diff-line-old-content group relative flex w-[50%] items-center pr-[10px]"
108
+ data-side={SplitSide[SplitSide.old]}
109
+ style={{ backgroundColor: oldLineContentBG }}
110
+ >
111
+ {hasDiff && enableAddWidget && (
112
+ <DiffSplitAddWidget
113
+ index={index}
114
+ lineNumber={oldLine.lineNumber}
115
+ side={SplitSide.old}
116
+ diffFile={diffFile}
117
+ onWidgetClick={(...props) => onAddWidgetClick.current?.(...props)}
118
+ className="absolute right-[100%] top-0 z-[1] translate-x-[50%]"
119
+ onOpenAddWidget={(lineNumber, side) => setWidget({ lineNumber: lineNumber, side: side })}
120
+ />
121
+ )}
122
+ <DiffContent
123
+ enableWrap={true}
124
+ diffFile={diffFile}
125
+ rawLine={oldLine.value}
126
+ diffLine={oldLine.diff}
127
+ plainLine={oldPlainLine}
128
+ syntaxLine={oldSyntaxLine}
129
+ enableHighlight={enableHighlight}
130
+ />
131
+ </div>
132
+ </>
133
+ ) : (
134
+ <>
135
+ <div
136
+ className="diff-line-old-num-placeholder w-[1%] min-w-[40px] select-none px-[10px]"
137
+ data-side={SplitSide[SplitSide.old]}
138
+ style={{
139
+ backgroundColor: `var(${emptyBGName})`,
140
+ width: `var(${diffAsideWidthName})`,
141
+ minWidth: `var(${diffAsideWidthName})`,
142
+ maxWidth: `var(${diffAsideWidthName})`,
143
+ }}
144
+ >
145
+ &ensp;
146
+ </div>
147
+ <div
148
+ className="diff-line-old-placeholder w-[50%] select-none pr-[10px]"
149
+ data-side={SplitSide[SplitSide.old]}
150
+ style={{ backgroundColor: `var(${emptyBGName})` }}
151
+ >
152
+ &ensp;
153
+ </div>
154
+ </>
155
+ )}
156
+ <div className="diff-split-line w-[1px] flex-shrink-0" style={{ backgroundColor: `var(${borderColorName})` }} />
157
+ {hasNewLine ? (
158
+ <>
159
+ <div
160
+ className="diff-line-new-num group relative flex w-[1%] min-w-[40px] select-none items-start px-[10px] text-right"
161
+ data-side={SplitSide[SplitSide.new]}
162
+ style={{
163
+ backgroundColor: newLineNumberBG,
164
+ color: `var(${hasDiff ? plainLineNumberColorName : expandLineNumberColorName})`,
165
+ width: `var(${diffAsideWidthName})`,
166
+ minWidth: `var(${diffAsideWidthName})`,
167
+ maxWidth: `var(${diffAsideWidthName})`,
168
+ }}
169
+ >
170
+ {hasDiff && enableAddWidget && (
171
+ <DiffSplitAddWidget
172
+ index={index}
173
+ lineNumber={newLine.lineNumber}
174
+ side={SplitSide.new}
175
+ diffFile={diffFile}
176
+ onWidgetClick={(...props) => onAddWidgetClick.current?.(...props)}
177
+ className="absolute left-[100%] z-[1] translate-x-[-50%]"
178
+ onOpenAddWidget={(lineNumber, side) => setWidget({ lineNumber: lineNumber, side: side })}
179
+ />
180
+ )}
181
+ <span
182
+ className="w-full"
183
+ data-line-num={newLine.lineNumber}
184
+ style={{ opacity: hasChange ? undefined : 0.5 }}
185
+ >
186
+ {newLine.lineNumber}
187
+ </span>
188
+ </div>
189
+ <div
190
+ className="diff-line-new-content group relative flex w-[50%] items-center pr-[10px]"
191
+ data-side={SplitSide[SplitSide.new]}
192
+ style={{ backgroundColor: newLineContentBG }}
193
+ >
194
+ {hasDiff && enableAddWidget && (
195
+ <DiffSplitAddWidget
196
+ index={index}
197
+ lineNumber={newLine.lineNumber}
198
+ side={SplitSide.new}
199
+ diffFile={diffFile}
200
+ onWidgetClick={(...props) => onAddWidgetClick.current?.(...props)}
201
+ className="absolute right-[100%] top-0 z-[1] translate-x-[50%]"
202
+ onOpenAddWidget={(lineNumber, side) => setWidget({ lineNumber: lineNumber, side: side })}
203
+ />
204
+ )}
205
+ <DiffContent
206
+ enableWrap={true}
207
+ diffFile={diffFile}
208
+ rawLine={newLine.value || ""}
209
+ diffLine={newLine.diff}
210
+ plainLine={newPlainLine}
211
+ syntaxLine={newSyntaxLine}
212
+ enableHighlight={enableHighlight}
213
+ />
214
+ </div>
215
+ </>
216
+ ) : (
217
+ <>
218
+ <div
219
+ className="diff-line-new-num-placeholder w-[1%] min-w-[40px] select-none px-[10px]"
220
+ data-side={SplitSide[SplitSide.new]}
221
+ style={{
222
+ backgroundColor: `var(${emptyBGName})`,
223
+ width: `var(${diffAsideWidthName})`,
224
+ minWidth: `var(${diffAsideWidthName})`,
225
+ maxWidth: `var(${diffAsideWidthName})`,
226
+ }}
227
+ >
228
+ &ensp;
229
+ </div>
230
+ <div
231
+ className="diff-line-new-placeholder w-[50%] select-none pr-[10px]"
232
+ data-side={SplitSide[SplitSide.new]}
233
+ style={{ backgroundColor: `var(${emptyBGName})` }}
234
+ >
235
+ &ensp;
236
+ </div>
237
+ </>
238
+ )}
239
+ </div>
240
+ );
241
+ };
242
+
243
+ export const DiffSplitContentLine = ({
244
+ index,
245
+ diffFile,
246
+ lineNumber,
247
+ }: {
248
+ index: number;
249
+ diffFile: DiffFile;
250
+ lineNumber: number;
251
+ }) => {
252
+ const oldLine = diffFile.getSplitLeftLine(index);
253
+
254
+ const newLine = diffFile.getSplitRightLine(index);
255
+
256
+ if (oldLine?.isHidden && newLine?.isHidden) return null;
257
+
258
+ return <InternalDiffSplitLine index={index} diffFile={diffFile} lineNumber={lineNumber} />;
259
+ };
@@ -0,0 +1,146 @@
1
+ import { emptyBGName } from "@git-diff-view/utils";
2
+ import * as React from "react";
3
+
4
+ import { useDomWidth } from "../../hooks/useDomWidth";
5
+ import { useSyncHeight } from "../../hooks/useSyncHeight";
6
+ import { SplitSide } from "../DiffView";
7
+ import { useDiffViewContext } from "../DiffViewContext";
8
+
9
+ import type { DiffFile } from "@git-diff-view/core";
10
+
11
+ const InternalDiffSplitExtendLine = ({
12
+ index,
13
+ diffFile,
14
+ oldLineExtend,
15
+ newLineExtend,
16
+ side,
17
+ lineNumber,
18
+ }: {
19
+ index: number;
20
+ side: SplitSide;
21
+ oldLineExtend: { data: any };
22
+ newLineExtend: { data: any };
23
+ diffFile: DiffFile;
24
+ lineNumber: number;
25
+ }) => {
26
+ const { useDiffContext } = useDiffViewContext();
27
+
28
+ const oldLine = diffFile.getSplitLeftLine(index);
29
+
30
+ const newLine = diffFile.getSplitRightLine(index);
31
+
32
+ const renderExtendLine = useDiffContext.useShallowStableSelector((s) => s.renderExtendLine);
33
+
34
+ const currentExtend = side === SplitSide.old ? oldLineExtend : newLineExtend;
35
+
36
+ const otherSide = side === SplitSide.old ? SplitSide.new : SplitSide.old;
37
+
38
+ const currentLineNumber = side === SplitSide.old ? oldLine.lineNumber : newLine.lineNumber;
39
+
40
+ const currentSideHasExtend = currentExtend?.data !== undefined && currentExtend?.data !== null;
41
+
42
+ const hasExtend =
43
+ (oldLineExtend?.data !== undefined && oldLineExtend?.data !== null) ||
44
+ (newLineExtend?.data !== undefined && newLineExtend?.data !== null);
45
+
46
+ useSyncHeight({
47
+ wrapper: `div[data-state="extend"][data-line="${lineNumber}-extend"]`,
48
+ selector: `div[data-line="${lineNumber}-extend-content"]`,
49
+ side: SplitSide[currentSideHasExtend ? side : otherSide],
50
+ enable: hasExtend && typeof renderExtendLine === "function",
51
+ });
52
+
53
+ const width = useDomWidth({
54
+ selector: side === SplitSide.old ? ".old-diff-table-wrapper" : ".new-diff-table-wrapper",
55
+ enable: currentSideHasExtend && typeof renderExtendLine === "function",
56
+ });
57
+
58
+ if (!renderExtendLine) return null;
59
+
60
+ return (
61
+ <div
62
+ data-line={`${lineNumber}-extend`}
63
+ data-state="extend"
64
+ data-side={SplitSide[side]}
65
+ className="diff-line diff-line-extend"
66
+ >
67
+ {currentSideHasExtend ? (
68
+ <div className={`diff-line-extend-${SplitSide[side]}-content p-0`}>
69
+ <div
70
+ data-line={`${lineNumber}-extend-content`}
71
+ data-side={SplitSide[side]}
72
+ className="diff-line-extend-wrapper sticky left-0 z-[1]"
73
+ style={{ width }}
74
+ >
75
+ {width > 0 &&
76
+ hasExtend &&
77
+ renderExtendLine?.({
78
+ diffFile,
79
+ side,
80
+ lineNumber: currentLineNumber,
81
+ data: currentExtend?.data,
82
+ onUpdate: diffFile.notifyAll,
83
+ })}
84
+ </div>
85
+ </div>
86
+ ) : (
87
+ <div
88
+ className={`diff-line-extend-${SplitSide[side]}-placeholder h-full select-none p-0`}
89
+ style={{ backgroundColor: `var(${emptyBGName})` }}
90
+ >
91
+ <div data-line={`${lineNumber}-extend-content`} data-side={SplitSide[side]} />
92
+ </div>
93
+ )}
94
+ </div>
95
+ );
96
+ };
97
+
98
+ export const DiffSplitExtendLine = ({
99
+ index,
100
+ diffFile,
101
+ side,
102
+ lineNumber,
103
+ }: {
104
+ index: number;
105
+ side: SplitSide;
106
+ diffFile: DiffFile;
107
+ lineNumber: number;
108
+ }) => {
109
+ const { useDiffContext } = useDiffViewContext();
110
+
111
+ const oldLine = diffFile.getSplitLeftLine(index);
112
+
113
+ const newLine = diffFile.getSplitRightLine(index);
114
+
115
+ const { oldLineExtend, newLineExtend } = useDiffContext(
116
+ React.useCallback(
117
+ (s) => ({
118
+ oldLineExtend: s.extendData?.oldFile?.[oldLine?.lineNumber],
119
+ newLineExtend: s.extendData?.newFile?.[newLine?.lineNumber],
120
+ }),
121
+ [oldLine?.lineNumber, newLine?.lineNumber]
122
+ )
123
+ );
124
+
125
+ const hasExtend = oldLineExtend?.data || newLineExtend?.data;
126
+
127
+ // if the expand action not enabled, the `isHidden` property will never change
128
+ const enableExpand = diffFile.getExpandEnabled();
129
+
130
+ const currentLine = side === SplitSide.old ? oldLine : newLine;
131
+
132
+ const currentIsShow = hasExtend && (!currentLine.isHidden || !enableExpand);
133
+
134
+ if (!currentIsShow) return null;
135
+
136
+ return (
137
+ <InternalDiffSplitExtendLine
138
+ side={side}
139
+ index={index}
140
+ diffFile={diffFile}
141
+ lineNumber={lineNumber}
142
+ oldLineExtend={oldLineExtend}
143
+ newLineExtend={newLineExtend}
144
+ />
145
+ );
146
+ };