@git-diff-view/react 0.0.25 → 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 +543 -376
  2. package/dist/cjs/index.development.js.map +1 -1
  3. package/dist/cjs/index.production.js +543 -376
  4. package/dist/cjs/index.production.js.map +1 -1
  5. package/dist/css/diff-view-pure.css +5 -1
  6. package/dist/css/diff-view.css +6 -2
  7. package/dist/esm/index.mjs +485 -321
  8. package/dist/esm/index.mjs.map +1 -1
  9. package/index.d.ts +317 -23
  10. package/package.json +8 -7
  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 +5 -1
  65. package/styles/diff-view.css +6 -2
@@ -0,0 +1,159 @@
1
+ /* eslint-disable @typescript-eslint/ban-ts-comment */
2
+ import { getUnifiedContentLine, SplitSide } from "@git-diff-view/core";
3
+ import { diffFontSizeName, removeAllSelection, diffAsideWidthName } from "@git-diff-view/utils";
4
+ import * as React from "react";
5
+ import { Fragment, memo, useEffect, useMemo, useRef } from "react";
6
+ import { useSyncExternalStore } from "use-sync-external-store/shim/index.js";
7
+
8
+ import { useTextWidth } from "../hooks/useTextWidth";
9
+
10
+ import { DiffUnifiedContentLine } from "./DiffUnifiedContentLine";
11
+ import { DiffUnifiedExtendLine } from "./DiffUnifiedExtendLine";
12
+ import { DiffUnifiedHunkLine } from "./DiffUnifiedHunkLine";
13
+ import { DiffUnifiedWidgetLine } from "./DiffUnifiedWidgetLine";
14
+ import { useDiffViewContext } from "./DiffViewContext";
15
+ import { DiffWidgetContext } from "./DiffWidgetContext";
16
+ import { createDiffWidgetStore } from "./tools";
17
+
18
+ import type { DiffFile } from "@git-diff-view/core";
19
+ import type { MouseEventHandler } from "react";
20
+
21
+ export const DiffUnifiedView = memo(({ diffFile }: { diffFile: DiffFile }) => {
22
+ const { useDiffContext } = useDiffViewContext();
23
+
24
+ const ref = useRef<HTMLStyleElement>(null);
25
+
26
+ const useDiffContextRef = useRef(useDiffContext);
27
+
28
+ useDiffContextRef.current = useDiffContext;
29
+
30
+ // performance optimization
31
+ const useWidget = useMemo(() => createDiffWidgetStore(useDiffContextRef), []);
32
+
33
+ const contextValue = useMemo(() => ({ useWidget }), [useWidget]);
34
+
35
+ const { fontSize, enableWrap, enableHighlight, enableAddWidget, onCreateUseWidgetHook } =
36
+ useDiffContext.useShallowStableSelector((s) => ({
37
+ fontSize: s.fontSize,
38
+ enableWrap: s.enableWrap,
39
+ enableHighlight: s.enableHighlight,
40
+ enableAddWidget: s.enableAddWidget,
41
+ onCreateUseWidgetHook: s.onCreateUseWidgetHook,
42
+ }));
43
+
44
+ useSyncExternalStore(diffFile.subscribe, diffFile.getUpdateCount, diffFile.getUpdateCount);
45
+
46
+ useEffect(() => {
47
+ const { setWidget } = useWidget.getReadonlyState();
48
+
49
+ setWidget({});
50
+ }, [diffFile, useWidget]);
51
+
52
+ useEffect(() => {
53
+ onCreateUseWidgetHook?.(useWidget);
54
+ }, [useWidget, onCreateUseWidgetHook]);
55
+
56
+ const unifiedLineLength = Math.max(diffFile.unifiedLineLength, diffFile.fileLineLength);
57
+
58
+ const _width = useTextWidth({
59
+ text: unifiedLineLength.toString(),
60
+ font: useMemo(() => ({ fontSize: fontSize + "px", fontFamily: "Menlo, Consolas, monospace" }), [fontSize]),
61
+ });
62
+
63
+ const width = Math.max(40, _width + 10);
64
+
65
+ const lines = getUnifiedContentLine(diffFile);
66
+
67
+ const setStyle = (side: SplitSide) => {
68
+ if (!ref.current) return;
69
+ if (!side) {
70
+ ref.current.textContent = "";
71
+ } else {
72
+ const id = `diff-root${diffFile.getId()}`;
73
+ 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}`;
74
+ }
75
+ };
76
+
77
+ const onMouseDown: MouseEventHandler<HTMLTableSectionElement> = (e) => {
78
+ let ele = e.target;
79
+
80
+ // need remove all the selection
81
+ if (ele && ele instanceof HTMLElement && ele.nodeName === "BUTTON") {
82
+ removeAllSelection();
83
+ return;
84
+ }
85
+
86
+ while (ele && ele instanceof HTMLElement) {
87
+ const state = ele.getAttribute("data-state");
88
+ if (state) {
89
+ if (state === "extend" || state === "hunk" || state === "widget") {
90
+ setStyle(undefined);
91
+ removeAllSelection();
92
+ return;
93
+ } else {
94
+ setStyle(SplitSide.new);
95
+ removeAllSelection();
96
+ return;
97
+ }
98
+ }
99
+
100
+ ele = ele.parentElement;
101
+ }
102
+ };
103
+
104
+ return (
105
+ <DiffWidgetContext.Provider value={contextValue}>
106
+ <div className={`unified-diff-view ${enableWrap ? "unified-diff-view-wrap" : "unified-diff-view-normal"} w-full`}>
107
+ <style data-select-style ref={ref} />
108
+ <div
109
+ className="unified-diff-table-wrapper diff-table-scroll-container w-full overflow-x-auto overflow-y-hidden"
110
+ style={{
111
+ // @ts-ignore
112
+ [diffAsideWidthName]: `${Math.round(width)}px`,
113
+ fontFamily: "Menlo, Consolas, monospace",
114
+ fontSize: `var(${diffFontSizeName})`,
115
+ }}
116
+ >
117
+ <table
118
+ className={`unified-diff-table w-full border-collapse border-spacing-0 ${enableWrap ? "table-fixed" : ""}`}
119
+ >
120
+ <colgroup>
121
+ <col className="unified-diff-table-num-col" />
122
+ <col className="unified-diff-table-content-col" />
123
+ </colgroup>
124
+ <thead className="hidden">
125
+ <tr>
126
+ <th scope="col">line number</th>
127
+ <th scope="col">line content</th>
128
+ </tr>
129
+ </thead>
130
+ <tbody className="diff-table-body leading-[1.4]" onMouseDownCapture={onMouseDown}>
131
+ {lines.map((item) => (
132
+ <Fragment key={item.index}>
133
+ <DiffUnifiedHunkLine index={item.index} lineNumber={item.lineNumber} diffFile={diffFile} />
134
+ <DiffUnifiedContentLine
135
+ index={item.index}
136
+ lineNumber={item.lineNumber}
137
+ diffFile={diffFile}
138
+ enableWrap={enableWrap}
139
+ enableHighlight={enableHighlight}
140
+ enableAddWidget={enableAddWidget}
141
+ />
142
+ <DiffUnifiedWidgetLine index={item.index} lineNumber={item.lineNumber} diffFile={diffFile} />
143
+ <DiffUnifiedExtendLine index={item.index} lineNumber={item.lineNumber} diffFile={diffFile} />
144
+ </Fragment>
145
+ ))}
146
+ <DiffUnifiedHunkLine
147
+ index={diffFile.unifiedLineLength}
148
+ lineNumber={diffFile.unifiedLineLength}
149
+ diffFile={diffFile}
150
+ />
151
+ </tbody>
152
+ </table>
153
+ </div>
154
+ </div>
155
+ </DiffWidgetContext.Provider>
156
+ );
157
+ });
158
+
159
+ DiffUnifiedView.displayName = "DiffUnifiedView";
@@ -0,0 +1,104 @@
1
+ import * as React from "react";
2
+
3
+ import { SplitSide } from "..";
4
+ import { useDomWidth } from "../hooks/useDomWidth";
5
+
6
+ import { useDiffViewContext } from "./DiffViewContext";
7
+ import { useDiffWidgetContext } from "./DiffWidgetContext";
8
+
9
+ import type { DiffFile } from "@git-diff-view/core";
10
+
11
+ const InternalDiffUnifiedWidgetLine = ({
12
+ index,
13
+ diffFile,
14
+ lineNumber,
15
+ }: {
16
+ index: number;
17
+ diffFile: DiffFile;
18
+ lineNumber: number;
19
+ }) => {
20
+ const { useWidget } = useDiffWidgetContext();
21
+
22
+ const setWidget = useWidget.getReadonlyState().setWidget;
23
+
24
+ const unifiedItem = diffFile.getUnifiedLine(index);
25
+
26
+ const onClose = () => setWidget({});
27
+
28
+ const widgetSide = useWidget.getReadonlyState().widgetSide;
29
+
30
+ const widgetLineNumber = useWidget.getReadonlyState().widgetLineNumber;
31
+
32
+ const oldWidget =
33
+ unifiedItem.oldLineNumber && widgetSide === SplitSide.old && widgetLineNumber === unifiedItem.oldLineNumber;
34
+
35
+ const newWidget =
36
+ unifiedItem.newLineNumber && widgetSide === SplitSide.new && widgetLineNumber === unifiedItem.newLineNumber;
37
+
38
+ const { useDiffContext } = useDiffViewContext();
39
+
40
+ // 需要显示的时候才进行方法订阅,可以大幅度提高性能
41
+ const renderWidgetLine = useDiffContext.useShallowStableSelector((s) => s.renderWidgetLine);
42
+
43
+ const width = useDomWidth({
44
+ selector: ".unified-diff-table-wrapper",
45
+ enable: typeof renderWidgetLine === "function",
46
+ });
47
+
48
+ if (!renderWidgetLine) return null;
49
+
50
+ return (
51
+ <tr data-line={`${lineNumber}-widget`} data-state="widget" className="diff-line diff-line-widget">
52
+ <td className="diff-line-widget-content p-0" colSpan={2}>
53
+ <div className="diff-line-widget-wrapper sticky left-0 z-[1]" style={{ width }}>
54
+ {width > 0 &&
55
+ oldWidget &&
56
+ renderWidgetLine?.({ diffFile, side: SplitSide.old, lineNumber: unifiedItem.oldLineNumber, onClose })}
57
+ {width > 0 &&
58
+ newWidget &&
59
+ renderWidgetLine?.({ diffFile, side: SplitSide.new, lineNumber: unifiedItem.newLineNumber, onClose })}
60
+ </div>
61
+ </td>
62
+ </tr>
63
+ );
64
+ };
65
+
66
+ export const DiffUnifiedWidgetLine = ({
67
+ index,
68
+ diffFile,
69
+ lineNumber,
70
+ }: {
71
+ index: number;
72
+ diffFile: DiffFile;
73
+ lineNumber: number;
74
+ }) => {
75
+ const { useWidget } = useDiffWidgetContext();
76
+
77
+ const currentIsShow = useWidget.useShallowSelector(
78
+ React.useCallback(
79
+ (s) => {
80
+ const widgetLineNumber = s.widgetLineNumber;
81
+
82
+ const widgetSide = s.widgetSide;
83
+
84
+ const unifiedItem = diffFile.getUnifiedLine(index);
85
+
86
+ const oldWidget =
87
+ unifiedItem.oldLineNumber && widgetSide === SplitSide.old && widgetLineNumber === unifiedItem.oldLineNumber;
88
+
89
+ const newWidget =
90
+ unifiedItem.newLineNumber && widgetSide === SplitSide.new && widgetLineNumber === unifiedItem.newLineNumber;
91
+
92
+ const currentIsShow = oldWidget || newWidget;
93
+
94
+ return currentIsShow;
95
+ },
96
+ [diffFile, index]
97
+ ),
98
+ (p, c) => p === c
99
+ );
100
+
101
+ if (!currentIsShow) return null;
102
+
103
+ return <InternalDiffUnifiedWidgetLine index={index} diffFile={diffFile} lineNumber={lineNumber} />;
104
+ };
@@ -0,0 +1,365 @@
1
+ /* eslint-disable @typescript-eslint/no-unnecessary-type-constraint */
2
+ /* eslint-disable @typescript-eslint/ban-ts-comment */
3
+ import { DiffFile, _cacheMap, SplitSide } from "@git-diff-view/core";
4
+ import { diffFontSizeName } from "@git-diff-view/utils";
5
+ import { memo, useEffect, useMemo, forwardRef, useImperativeHandle, useRef } from "react";
6
+ import * as React from "react";
7
+
8
+ import { useIsMounted } from "../hooks/useIsMounted";
9
+ import { useUnmount } from "../hooks/useUnmount";
10
+
11
+ import { DiffSplitView } from "./DiffSplitView";
12
+ import { DiffUnifiedView } from "./DiffUnifiedView";
13
+ import { DiffViewContext } from "./DiffViewContext";
14
+ import { createDiffConfigStore } from "./tools";
15
+ // import { DiffSplitView } from "./v2/DiffSplitView_v2";
16
+
17
+ import type { createDiffWidgetStore } from "./tools";
18
+ import type { DiffHighlighter, DiffHighlighterLang } from "@git-diff-view/core";
19
+ import type { CSSProperties, ForwardedRef, ReactNode } from "react";
20
+
21
+ _cacheMap.name = "@git-diff-view/react";
22
+
23
+ export { SplitSide };
24
+
25
+ export enum DiffModeEnum {
26
+ // github like
27
+ SplitGitHub = 1,
28
+ // gitlab like
29
+ SplitGitLab = 2,
30
+ Split = 1 | 2,
31
+ Unified = 4,
32
+ }
33
+
34
+ export type DiffViewProps<T> = {
35
+ data?: {
36
+ oldFile?: { fileName?: string | null; fileLang?: DiffHighlighterLang | string | null; content?: string | null };
37
+ newFile?: { fileName?: string | null; fileLang?: DiffHighlighterLang | string | null; content?: string | null };
38
+ hunks: string[];
39
+ };
40
+ extendData?: { oldFile?: Record<string, { data: T }>; newFile?: Record<string, { data: T }> };
41
+ diffFile?: DiffFile;
42
+ className?: string;
43
+ style?: CSSProperties;
44
+ /**
45
+ * provide a custom highlighter
46
+ * eg: lowlight, refractor, starry-night, shiki
47
+ */
48
+ registerHighlighter?: Omit<DiffHighlighter, "getHighlighterEngine">;
49
+ diffViewMode?: DiffModeEnum;
50
+ diffViewWrap?: boolean;
51
+ diffViewTheme?: "light" | "dark";
52
+ diffViewFontSize?: number;
53
+ diffViewHighlight?: boolean;
54
+ diffViewAddWidget?: boolean;
55
+ renderWidgetLine?: ({
56
+ diffFile,
57
+ side,
58
+ lineNumber,
59
+ onClose,
60
+ }: {
61
+ lineNumber: number;
62
+ side: SplitSide;
63
+ diffFile: DiffFile;
64
+ onClose: () => void;
65
+ }) => ReactNode;
66
+ renderExtendLine?: ({
67
+ diffFile,
68
+ side,
69
+ data,
70
+ lineNumber,
71
+ onUpdate,
72
+ }: {
73
+ lineNumber: number;
74
+ side: SplitSide;
75
+ data: T;
76
+ diffFile: DiffFile;
77
+ onUpdate: () => void;
78
+ }) => ReactNode;
79
+ onAddWidgetClick?: (lineNumber: number, side: SplitSide) => void;
80
+ onCreateUseWidgetHook?: (hook: ReturnType<typeof createDiffWidgetStore>) => void;
81
+ };
82
+
83
+ type DiffViewProps_1<T> = Omit<DiffViewProps<T>, "data"> & {
84
+ data?: {
85
+ oldFile?: { fileName?: string | null; fileLang?: DiffHighlighterLang | null; content?: string | null };
86
+ newFile?: { fileName?: string | null; fileLang?: DiffHighlighterLang | null; content?: string | null };
87
+ hunks: string[];
88
+ };
89
+ };
90
+
91
+ type DiffViewProps_2<T> = Omit<DiffViewProps<T>, "data"> & {
92
+ data?: {
93
+ oldFile?: { fileName?: string | null; fileLang?: string | null; content?: string | null };
94
+ newFile?: { fileName?: string | null; fileLang?: string | null; content?: string | null };
95
+ hunks: string[];
96
+ };
97
+ };
98
+
99
+ const InternalDiffView = <T extends unknown>(
100
+ props: Omit<DiffViewProps<T>, "data" | "registerHighlighter"> & { isMounted: boolean }
101
+ ) => {
102
+ const {
103
+ diffFile,
104
+ className,
105
+ style,
106
+ diffViewMode,
107
+ diffViewWrap,
108
+ diffViewFontSize,
109
+ diffViewHighlight,
110
+ renderWidgetLine,
111
+ renderExtendLine,
112
+ extendData,
113
+ diffViewAddWidget,
114
+ onAddWidgetClick,
115
+ onCreateUseWidgetHook,
116
+ isMounted,
117
+ } = props;
118
+
119
+ const diffFileId = useMemo(() => diffFile.getId(), [diffFile]);
120
+
121
+ const wrapperRef = useRef<HTMLDivElement>();
122
+
123
+ // performance optimization
124
+ const useDiffContext = useMemo(
125
+ () => createDiffConfigStore(props, diffFileId),
126
+ // eslint-disable-next-line react-hooks/exhaustive-deps
127
+ []
128
+ );
129
+
130
+ useEffect(() => {
131
+ const {
132
+ id,
133
+ setId,
134
+ mode,
135
+ setMode,
136
+ mounted,
137
+ setMounted,
138
+ enableAddWidget,
139
+ setEnableAddWidget,
140
+ enableHighlight,
141
+ setEnableHighlight,
142
+ enableWrap,
143
+ setEnableWrap,
144
+ setExtendData,
145
+ fontSize,
146
+ setFontSize,
147
+ onAddWidgetClick: _onAddWidgetClick,
148
+ setOnAddWidgetClick,
149
+ renderExtendLine: _renderExtendLine,
150
+ setRenderExtendLine,
151
+ renderWidgetLine: _renderWidgetLine,
152
+ setRenderWidgetLine,
153
+ onCreateUseWidgetHook: _onCreateUseWidgetHook,
154
+ setOnCreateUseWidgetHook,
155
+ } = useDiffContext.getReadonlyState();
156
+
157
+ if (diffFileId && diffFileId !== id) {
158
+ setId(diffFileId);
159
+ }
160
+
161
+ if (diffViewMode && diffViewMode !== mode) {
162
+ setMode(diffViewMode);
163
+ }
164
+
165
+ if (mounted !== isMounted) {
166
+ setMounted(isMounted);
167
+ }
168
+
169
+ if (diffViewAddWidget !== enableAddWidget) {
170
+ setEnableAddWidget(diffViewAddWidget);
171
+ }
172
+
173
+ if (diffViewHighlight !== enableHighlight) {
174
+ setEnableHighlight(diffViewHighlight);
175
+ }
176
+
177
+ if (diffViewWrap !== enableWrap) {
178
+ setEnableWrap(diffViewWrap);
179
+ }
180
+
181
+ if (extendData) {
182
+ setExtendData(extendData);
183
+ }
184
+
185
+ if (diffViewFontSize && diffViewFontSize !== fontSize) {
186
+ setFontSize(diffViewFontSize);
187
+ }
188
+
189
+ if (onAddWidgetClick !== _onAddWidgetClick.current) {
190
+ setOnAddWidgetClick({ current: onAddWidgetClick });
191
+ }
192
+
193
+ if (onCreateUseWidgetHook !== _onCreateUseWidgetHook) {
194
+ setOnCreateUseWidgetHook(onCreateUseWidgetHook);
195
+ }
196
+
197
+ if (renderExtendLine !== _renderExtendLine) {
198
+ setRenderExtendLine(renderExtendLine);
199
+ }
200
+
201
+ if (renderWidgetLine !== _renderWidgetLine) {
202
+ setRenderWidgetLine(renderWidgetLine);
203
+ }
204
+ }, [
205
+ useDiffContext,
206
+ diffViewFontSize,
207
+ diffViewHighlight,
208
+ diffViewMode,
209
+ diffViewWrap,
210
+ diffViewAddWidget,
211
+ diffFileId,
212
+ isMounted,
213
+ renderWidgetLine,
214
+ renderExtendLine,
215
+ extendData,
216
+ onAddWidgetClick,
217
+ onCreateUseWidgetHook,
218
+ ]);
219
+
220
+ useEffect(() => {
221
+ const cb = diffFile.subscribe(() => {
222
+ wrapperRef.current?.setAttribute("data-theme", diffFile._getTheme() || "light");
223
+ wrapperRef.current?.setAttribute("data-highlighter", diffFile._getHighlighterName());
224
+ });
225
+
226
+ return cb;
227
+ }, [diffFile]);
228
+
229
+ const value = useMemo(() => ({ useDiffContext }), [useDiffContext]);
230
+
231
+ return (
232
+ <DiffViewContext.Provider value={value}>
233
+ <div
234
+ className="diff-tailwindcss-wrapper"
235
+ data-component="git-diff-view"
236
+ data-theme={diffFile._getTheme() || "light"}
237
+ data-version={__VERSION__}
238
+ data-highlighter={diffFile._getHighlighterName()}
239
+ ref={wrapperRef}
240
+ >
241
+ <div
242
+ className="diff-style-root"
243
+ style={{
244
+ // @ts-ignore
245
+ [diffFontSizeName]: diffViewFontSize + "px",
246
+ }}
247
+ >
248
+ <div
249
+ id={isMounted ? `diff-root${diffFileId}` : undefined}
250
+ className={"diff-view-wrapper" + (className ? ` ${className}` : "")}
251
+ style={style}
252
+ >
253
+ {diffViewMode & DiffModeEnum.Split ? (
254
+ <DiffSplitView diffFile={diffFile} />
255
+ ) : (
256
+ <DiffUnifiedView diffFile={diffFile} />
257
+ )}
258
+ </div>
259
+ </div>
260
+ </div>
261
+ </DiffViewContext.Provider>
262
+ );
263
+ };
264
+
265
+ const MemoedInternalDiffView = memo(InternalDiffView);
266
+
267
+ const DiffViewWithRef = <T extends unknown>(
268
+ props: DiffViewProps<T>,
269
+ ref: ForwardedRef<{ getDiffFileInstance: () => DiffFile }>
270
+ ) => {
271
+ const { registerHighlighter, data, diffViewTheme, diffFile: _diffFile, ...restProps } = props;
272
+
273
+ const diffFile = useMemo(() => {
274
+ if (_diffFile) {
275
+ // missing data for plain file render
276
+ // TODO next release update ?
277
+ // will cause more complex for diffFile flow control, keep current
278
+ const diffFile = DiffFile.createInstance({});
279
+ diffFile._mergeFullBundle(_diffFile._getFullBundle());
280
+ return diffFile;
281
+ } else if (data) {
282
+ return new DiffFile(
283
+ data?.oldFile?.fileName || "",
284
+ data?.oldFile?.content || "",
285
+ data?.newFile?.fileName || "",
286
+ data?.newFile?.content || "",
287
+ data?.hunks || [],
288
+ data?.oldFile?.fileLang || "",
289
+ data?.newFile?.fileLang || ""
290
+ );
291
+ }
292
+ return null;
293
+ }, [data, _diffFile]);
294
+
295
+ const diffFileRef = useRef(diffFile);
296
+
297
+ if (diffFileRef.current && diffFileRef.current !== diffFile) {
298
+ diffFileRef.current.clear?.();
299
+ diffFileRef.current = diffFile;
300
+ }
301
+
302
+ const isMounted = useIsMounted();
303
+
304
+ useEffect(() => {
305
+ if (_diffFile && diffFile) {
306
+ _diffFile._addClonedInstance(diffFile);
307
+ return () => {
308
+ _diffFile._delClonedInstance(diffFile);
309
+ };
310
+ }
311
+ }, [diffFile, _diffFile]);
312
+
313
+ useEffect(() => {
314
+ if (!diffFile) return;
315
+ diffFile.initTheme(diffViewTheme);
316
+ diffFile.initRaw();
317
+ diffFile.buildSplitDiffLines();
318
+ diffFile.buildUnifiedDiffLines();
319
+ }, [diffFile, diffViewTheme]);
320
+
321
+ useEffect(() => {
322
+ if (!diffFile) return;
323
+ if (props.diffViewHighlight) {
324
+ diffFile.initSyntax({ registerHighlighter });
325
+ diffFile.notifyAll();
326
+ }
327
+ }, [diffFile, props.diffViewHighlight, registerHighlighter, diffViewTheme]);
328
+
329
+ // fix react strict mode error
330
+ useUnmount(() => (__DEV__ ? diffFile?._destroy?.() : diffFile?.clear?.()), [diffFile]);
331
+
332
+ useImperativeHandle(ref, () => ({ getDiffFileInstance: () => diffFile }), [diffFile]);
333
+
334
+ if (!diffFile) return null;
335
+
336
+ return (
337
+ <MemoedInternalDiffView
338
+ key={diffFile.getId()}
339
+ {...restProps}
340
+ diffFile={diffFile}
341
+ isMounted={isMounted}
342
+ diffViewMode={restProps.diffViewMode || DiffModeEnum.SplitGitHub}
343
+ diffViewFontSize={restProps.diffViewFontSize || 14}
344
+ />
345
+ );
346
+ };
347
+
348
+ // type helper function
349
+ function ReactDiffView<T>(
350
+ props: DiffViewProps_1<T> & { ref?: ForwardedRef<{ getDiffFileInstance: () => DiffFile }> }
351
+ ): JSX.Element;
352
+ function ReactDiffView<T>(
353
+ props: DiffViewProps_2<T> & { ref?: ForwardedRef<{ getDiffFileInstance: () => DiffFile }> }
354
+ ): JSX.Element;
355
+ function ReactDiffView<T>(props: DiffViewProps<T> & { ref?: ForwardedRef<{ getDiffFileInstance: () => DiffFile }> }) {
356
+ return <DiffViewWithRef {...props} />;
357
+ }
358
+
359
+ const InnerDiffView = forwardRef(DiffViewWithRef);
360
+
361
+ InnerDiffView.displayName = "DiffView";
362
+
363
+ export const DiffView = InnerDiffView as typeof ReactDiffView;
364
+
365
+ export const version = __VERSION__;
@@ -0,0 +1,11 @@
1
+ import { createContext, useContext } from "react";
2
+
3
+ import type { createDiffConfigStore } from "./tools";
4
+
5
+ export const DiffViewContext = createContext<{
6
+ useDiffContext: ReturnType<typeof createDiffConfigStore>;
7
+ }>(null);
8
+
9
+ DiffViewContext.displayName = "DiffViewContext";
10
+
11
+ export const useDiffViewContext = () => useContext(DiffViewContext);
@@ -0,0 +1,17 @@
1
+ import { createContext, useContext } from "react";
2
+
3
+ import type { SplitSide } from "..";
4
+ import type { Ref, UseSelectorWithStore } from "reactivity-store";
5
+
6
+ export const DiffWidgetContext = createContext<{
7
+ useWidget: UseSelectorWithStore<{
8
+ widgetSide: Ref<SplitSide>;
9
+ widgetLineNumber: Ref<number>;
10
+
11
+ setWidget: (props: { side?: SplitSide; lineNumber?: number }) => void;
12
+ }>;
13
+ }>(null);
14
+
15
+ DiffWidgetContext.displayName = "DiffWidgetContext";
16
+
17
+ export const useDiffWidgetContext = () => useContext(DiffWidgetContext);