@particle-academy/fancy-code 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -0
- package/dist/index.cjs +106 -45
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +52 -7
- package/dist/index.d.ts +52 -7
- package/dist/index.js +100 -45
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -44,6 +44,26 @@ function App() {
|
|
|
44
44
|
|
|
45
45
|
The component ships with no default border or rounding — add your own via `className` for standalone use, or embed directly into IDE layouts without visual conflicts.
|
|
46
46
|
|
|
47
|
+
## Diff gutter
|
|
48
|
+
|
|
49
|
+
Pass `diffBase` (the baseline document) and the line-number column shows live,
|
|
50
|
+
VS Code-style change marks of the **current value vs that baseline** — a green
|
|
51
|
+
bar for added lines, a blue bar for modified lines, and a red wedge where lines
|
|
52
|
+
were deleted. Recomputed as the user types (line-level, cheap — the engine is
|
|
53
|
+
[`@particle-academy/fancy-file-commons`](https://github.com/Particle-Academy/fancy-file-commons),
|
|
54
|
+
the same core `fancy-diff` renders).
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
<CodeEditor value={code} onChange={setCode} diffBase={savedCode} language="typescript">
|
|
58
|
+
<CodeEditor.Panel />
|
|
59
|
+
</CodeEditor>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Totals are exposed on the context as `diffStats` (`{ added, modified, removed }`)
|
|
63
|
+
for a status-bar chip or save indicator, and every marked row carries stable
|
|
64
|
+
`data-diff` / `data-diff-deleted-above` handles. Themes may override the colors
|
|
65
|
+
via `diffAdded` / `diffModified` / `diffRemoved`.
|
|
66
|
+
|
|
47
67
|
## Unified file viewer
|
|
48
68
|
|
|
49
69
|
Showing arbitrary files from a tree? `FileViewer` renders text in the
|
package/dist/index.cjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var react = require('react');
|
|
4
4
|
var reactFancy = require('@particle-academy/react-fancy');
|
|
5
|
+
var fancyFileCommons = require('@particle-academy/fancy-file-commons');
|
|
5
6
|
var jsxRuntime = require('react/jsx-runtime');
|
|
6
7
|
|
|
7
8
|
// src/components/CodeEditor/CodeEditor.tsx
|
|
@@ -13,6 +14,24 @@ function useCodeEditor() {
|
|
|
13
14
|
}
|
|
14
15
|
return ctx;
|
|
15
16
|
}
|
|
17
|
+
|
|
18
|
+
// src/components/CodeEditor/diff-gutter.ts
|
|
19
|
+
function gutterDiffMark(annotations, line) {
|
|
20
|
+
if (!annotations) return null;
|
|
21
|
+
const a = annotations.byLine[line];
|
|
22
|
+
if (!a) return null;
|
|
23
|
+
return { type: a.type, deletedAbove: (a.deletedAbove ?? 0) > 0 };
|
|
24
|
+
}
|
|
25
|
+
function hasDeletedAtEnd(annotations) {
|
|
26
|
+
return (annotations?.deletedAtEnd ?? 0) > 0;
|
|
27
|
+
}
|
|
28
|
+
var FALLBACK = { added: "#3fb950", modified: "#4184e4", removed: "#f85149" };
|
|
29
|
+
function diffMarkColor(type, colors) {
|
|
30
|
+
return type === "added" ? colors.diffAdded ?? FALLBACK.added : colors.diffModified ?? FALLBACK.modified;
|
|
31
|
+
}
|
|
32
|
+
function diffRemovedColor(colors) {
|
|
33
|
+
return colors.diffRemoved ?? FALLBACK.removed;
|
|
34
|
+
}
|
|
16
35
|
function CodeEditorPanel({ className }) {
|
|
17
36
|
const {
|
|
18
37
|
_engineReturn: engine,
|
|
@@ -20,6 +39,7 @@ function CodeEditorPanel({ className }) {
|
|
|
20
39
|
wordWrap,
|
|
21
40
|
readOnly,
|
|
22
41
|
placeholder,
|
|
42
|
+
_diffAnnotations: diffAnnotations,
|
|
23
43
|
_maxHeight,
|
|
24
44
|
_minHeight
|
|
25
45
|
} = useCodeEditor();
|
|
@@ -40,25 +60,80 @@ function CodeEditorPanel({ className }) {
|
|
|
40
60
|
const gutterWidth = lineNumbers ? `${Math.max(String(lineCount).length, 2) * 0.75 + 1.5}em` : "0";
|
|
41
61
|
const lineNumberElements = react.useMemo(() => {
|
|
42
62
|
if (!lineNumbers) return null;
|
|
63
|
+
const removed = diffRemovedColor(themeColors);
|
|
43
64
|
const lines = [];
|
|
44
65
|
for (let i = 1; i <= lineCount; i++) {
|
|
66
|
+
const mark = gutterDiffMark(diffAnnotations, i);
|
|
45
67
|
lines.push(
|
|
46
|
-
/* @__PURE__ */ jsxRuntime.
|
|
68
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
47
69
|
"div",
|
|
48
70
|
{
|
|
71
|
+
"data-diff": mark?.type,
|
|
72
|
+
"data-diff-deleted-above": mark?.deletedAbove ? "" : void 0,
|
|
49
73
|
style: {
|
|
74
|
+
position: "relative",
|
|
50
75
|
color: i === activeLine ? themeColors.foreground : themeColors.gutterForeground,
|
|
51
76
|
backgroundColor: i === activeLine ? themeColors.activeLineBackground : void 0
|
|
52
77
|
},
|
|
53
78
|
className: "pr-3 text-right text-[13px] leading-[1.5] select-none",
|
|
54
|
-
children:
|
|
79
|
+
children: [
|
|
80
|
+
mark?.type && /* @__PURE__ */ jsxRuntime.jsx(
|
|
81
|
+
"span",
|
|
82
|
+
{
|
|
83
|
+
"aria-hidden": "true",
|
|
84
|
+
style: {
|
|
85
|
+
position: "absolute",
|
|
86
|
+
left: 0,
|
|
87
|
+
top: 0,
|
|
88
|
+
bottom: 0,
|
|
89
|
+
width: 3,
|
|
90
|
+
borderRadius: 2,
|
|
91
|
+
backgroundColor: diffMarkColor(mark.type, themeColors)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
),
|
|
95
|
+
mark?.deletedAbove && /* @__PURE__ */ jsxRuntime.jsx(
|
|
96
|
+
"span",
|
|
97
|
+
{
|
|
98
|
+
"aria-hidden": "true",
|
|
99
|
+
style: {
|
|
100
|
+
position: "absolute",
|
|
101
|
+
left: 0,
|
|
102
|
+
top: -4,
|
|
103
|
+
width: 0,
|
|
104
|
+
height: 0,
|
|
105
|
+
borderLeft: `8px solid ${removed}`,
|
|
106
|
+
borderTop: "4px solid transparent",
|
|
107
|
+
borderBottom: "4px solid transparent"
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
),
|
|
111
|
+
i,
|
|
112
|
+
i === lineCount && hasDeletedAtEnd(diffAnnotations) && /* @__PURE__ */ jsxRuntime.jsx(
|
|
113
|
+
"span",
|
|
114
|
+
{
|
|
115
|
+
"aria-hidden": "true",
|
|
116
|
+
"data-diff-deleted-at-end": "",
|
|
117
|
+
style: {
|
|
118
|
+
position: "absolute",
|
|
119
|
+
left: 0,
|
|
120
|
+
bottom: -4,
|
|
121
|
+
width: 0,
|
|
122
|
+
height: 0,
|
|
123
|
+
borderLeft: `8px solid ${removed}`,
|
|
124
|
+
borderTop: "4px solid transparent",
|
|
125
|
+
borderBottom: "4px solid transparent"
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
)
|
|
129
|
+
]
|
|
55
130
|
},
|
|
56
131
|
i
|
|
57
132
|
)
|
|
58
133
|
);
|
|
59
134
|
}
|
|
60
135
|
return lines;
|
|
61
|
-
}, [lineNumbers, lineCount, activeLine, themeColors]);
|
|
136
|
+
}, [lineNumbers, lineCount, activeLine, themeColors, diffAnnotations]);
|
|
62
137
|
const containerStyle = {
|
|
63
138
|
backgroundColor: themeColors.background,
|
|
64
139
|
color: themeColors.foreground,
|
|
@@ -1260,6 +1335,9 @@ registerTheme({
|
|
|
1260
1335
|
activeLineBackground: "#f8fafc",
|
|
1261
1336
|
selectionBackground: "#dbeafe",
|
|
1262
1337
|
cursorColor: "#3b82f6",
|
|
1338
|
+
diffAdded: "#1a7f37",
|
|
1339
|
+
diffModified: "#0969da",
|
|
1340
|
+
diffRemoved: "#cf222e",
|
|
1263
1341
|
keyword: "#8b5cf6",
|
|
1264
1342
|
string: "#059669",
|
|
1265
1343
|
comment: "#94a3b8",
|
|
@@ -1288,6 +1366,9 @@ registerTheme({
|
|
|
1288
1366
|
activeLineBackground: "#27272a",
|
|
1289
1367
|
selectionBackground: "#1e3a5f",
|
|
1290
1368
|
cursorColor: "#60a5fa",
|
|
1369
|
+
diffAdded: "#3fb950",
|
|
1370
|
+
diffModified: "#4184e4",
|
|
1371
|
+
diffRemoved: "#f85149",
|
|
1291
1372
|
keyword: "#c084fc",
|
|
1292
1373
|
string: "#34d399",
|
|
1293
1374
|
comment: "#71717a",
|
|
@@ -1617,9 +1698,14 @@ function CodeEditorRoot({
|
|
|
1617
1698
|
minHeight,
|
|
1618
1699
|
maxHeight,
|
|
1619
1700
|
cursorLine,
|
|
1620
|
-
cursorColumn
|
|
1701
|
+
cursorColumn,
|
|
1702
|
+
diffBase
|
|
1621
1703
|
}) {
|
|
1622
1704
|
const [currentValue, setCurrentValue] = reactFancy.useControllableState(valueProp, defaultValue, onChange);
|
|
1705
|
+
const diffAnnotations = react.useMemo(
|
|
1706
|
+
() => diffBase == null ? null : fancyFileCommons.annotateLines(diffBase, currentValue),
|
|
1707
|
+
[diffBase, currentValue]
|
|
1708
|
+
);
|
|
1623
1709
|
const isDark = useDarkMode();
|
|
1624
1710
|
const resolvedTheme = theme === "auto" ? isDark ? "dark" : "light" : theme;
|
|
1625
1711
|
const [currentLanguage, setCurrentLanguageState] = react.useState(() => {
|
|
@@ -1698,11 +1784,17 @@ function CodeEditorRoot({
|
|
|
1698
1784
|
selectionLength,
|
|
1699
1785
|
textareaRef: engineReturn.textareaRef,
|
|
1700
1786
|
placeholder,
|
|
1787
|
+
diffStats: diffAnnotations ? {
|
|
1788
|
+
added: diffAnnotations.added,
|
|
1789
|
+
modified: diffAnnotations.modified,
|
|
1790
|
+
removed: diffAnnotations.removed
|
|
1791
|
+
} : null,
|
|
1792
|
+
_diffAnnotations: diffAnnotations,
|
|
1701
1793
|
_engineReturn: engineReturn,
|
|
1702
1794
|
_minHeight: minHeight,
|
|
1703
1795
|
_maxHeight: maxHeight
|
|
1704
1796
|
}),
|
|
1705
|
-
[engineReturn, currentValue, currentLanguage, setLanguage, resolvedTheme, readOnly, showLineNumbers, isWordWrap, tabSizeProp, cursorPosition, selectionLength, setCurrentValue, placeholder, minHeight, maxHeight]
|
|
1797
|
+
[engineReturn, currentValue, currentLanguage, setLanguage, resolvedTheme, readOnly, showLineNumbers, isWordWrap, tabSizeProp, cursorPosition, selectionLength, setCurrentValue, placeholder, diffAnnotations, minHeight, maxHeight]
|
|
1706
1798
|
);
|
|
1707
1799
|
return /* @__PURE__ */ jsxRuntime.jsx(CodeEditorContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1708
1800
|
"div",
|
|
@@ -1878,48 +1970,10 @@ function MarkdownEditor({
|
|
|
1878
1970
|
);
|
|
1879
1971
|
}
|
|
1880
1972
|
MarkdownEditor.displayName = "MarkdownEditor";
|
|
1881
|
-
var EXT_LANGUAGE = {
|
|
1882
|
-
js: "javascript",
|
|
1883
|
-
mjs: "javascript",
|
|
1884
|
-
cjs: "javascript",
|
|
1885
|
-
jsx: "javascript",
|
|
1886
|
-
json: "javascript",
|
|
1887
|
-
// the JS tokenizer highlights JSON acceptably
|
|
1888
|
-
jsonc: "javascript",
|
|
1889
|
-
ts: "typescript",
|
|
1890
|
-
mts: "typescript",
|
|
1891
|
-
cts: "typescript",
|
|
1892
|
-
tsx: "typescript",
|
|
1893
|
-
html: "html",
|
|
1894
|
-
htm: "html",
|
|
1895
|
-
xml: "html",
|
|
1896
|
-
xhtml: "html",
|
|
1897
|
-
vue: "html",
|
|
1898
|
-
php: "php",
|
|
1899
|
-
phtml: "php",
|
|
1900
|
-
py: "python",
|
|
1901
|
-
pyw: "python",
|
|
1902
|
-
go: "go",
|
|
1903
|
-
md: "markdown",
|
|
1904
|
-
markdown: "markdown",
|
|
1905
|
-
mkd: "markdown",
|
|
1906
|
-
mdx: "markdown"
|
|
1907
|
-
};
|
|
1908
|
-
function basename(filename) {
|
|
1909
|
-
return filename.split(/[?#]/, 1)[0].split(/[\\/]/).pop() ?? "";
|
|
1910
|
-
}
|
|
1911
|
-
function languageFromFilename(filename) {
|
|
1912
|
-
if (!filename) return "plaintext";
|
|
1913
|
-
const seg = basename(filename);
|
|
1914
|
-
const dot = seg.lastIndexOf(".");
|
|
1915
|
-
if (dot <= 0) return "plaintext";
|
|
1916
|
-
const ext = seg.slice(dot + 1).toLowerCase();
|
|
1917
|
-
return EXT_LANGUAGE[ext] ?? "plaintext";
|
|
1918
|
-
}
|
|
1919
1973
|
function resolveFileKind({ filename, mime }) {
|
|
1920
1974
|
const media = reactFancy.resolveMediaType({ mime, src: filename });
|
|
1921
1975
|
if (media !== "unknown") return { kind: "media", mediaKind: media };
|
|
1922
|
-
return { kind: "text", language: languageFromFilename(filename) };
|
|
1976
|
+
return { kind: "text", language: fancyFileCommons.languageFromFilename(filename) };
|
|
1923
1977
|
}
|
|
1924
1978
|
function FileViewer({
|
|
1925
1979
|
filename,
|
|
@@ -1999,15 +2053,22 @@ function CodeEditorInputTag(props) {
|
|
|
1999
2053
|
return /* @__PURE__ */ jsxRuntime.jsx(reactFancy.InputTag, { adapter, ...props });
|
|
2000
2054
|
}
|
|
2001
2055
|
|
|
2056
|
+
Object.defineProperty(exports, "languageFromFilename", {
|
|
2057
|
+
enumerable: true,
|
|
2058
|
+
get: function () { return fancyFileCommons.languageFromFilename; }
|
|
2059
|
+
});
|
|
2002
2060
|
exports.CodeEditor = CodeEditor;
|
|
2003
2061
|
exports.CodeEditorInputTag = CodeEditorInputTag;
|
|
2004
2062
|
exports.FileViewer = FileViewer;
|
|
2005
2063
|
exports.MarkdownEditor = MarkdownEditor;
|
|
2064
|
+
exports.diffMarkColor = diffMarkColor;
|
|
2065
|
+
exports.diffRemovedColor = diffRemovedColor;
|
|
2006
2066
|
exports.getLanguage = getLanguage;
|
|
2007
2067
|
exports.getRegisteredLanguages = getRegisteredLanguages;
|
|
2008
2068
|
exports.getRegisteredThemes = getRegisteredThemes;
|
|
2009
2069
|
exports.getTheme = getTheme;
|
|
2010
|
-
exports.
|
|
2070
|
+
exports.gutterDiffMark = gutterDiffMark;
|
|
2071
|
+
exports.hasDeletedAtEnd = hasDeletedAtEnd;
|
|
2011
2072
|
exports.registerLanguage = registerLanguage;
|
|
2012
2073
|
exports.registerTheme = registerTheme;
|
|
2013
2074
|
exports.renderMarkdown = renderMarkdown;
|