@particle-academy/fancy-code 0.7.0 → 0.9.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 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.jsx(
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: i
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,
@@ -371,89 +446,6 @@ var tokenizeJavaScript = (source) => {
371
446
  return tokens;
372
447
  };
373
448
 
374
- // src/engine/tokenizers/html.ts
375
- var tokenizeHtml = (source) => {
376
- const tokens = [];
377
- const len = source.length;
378
- let i = 0;
379
- while (i < len) {
380
- if (source[i] === "<" && source.slice(i, i + 4) === "<!--") {
381
- const pos2 = i;
382
- i += 4;
383
- while (i < len && source.slice(i, i + 3) !== "-->") i++;
384
- i += 3;
385
- tokens.push({ type: "comment", start: pos2, end: i });
386
- continue;
387
- }
388
- if (source[i] === "<" && source.slice(i, i + 9).toUpperCase() === "<!DOCTYPE") {
389
- const pos2 = i;
390
- while (i < len && source[i] !== ">") i++;
391
- i++;
392
- tokens.push({ type: "keyword", start: pos2, end: i });
393
- continue;
394
- }
395
- if (source[i] === "<") {
396
- tokens.push({ type: "punctuation", start: i, end: i + 1 });
397
- i++;
398
- if (i < len && source[i] === "/") {
399
- tokens.push({ type: "punctuation", start: i, end: i + 1 });
400
- i++;
401
- }
402
- if (i < len && /[a-zA-Z]/.test(source[i])) {
403
- const pos2 = i;
404
- while (i < len && /[a-zA-Z0-9-]/.test(source[i])) i++;
405
- tokens.push({ type: "tag", start: pos2, end: i });
406
- }
407
- while (i < len && source[i] !== ">" && !(source[i] === "/" && source[i + 1] === ">")) {
408
- if (source[i] === " " || source[i] === " " || source[i] === "\n" || source[i] === "\r") {
409
- i++;
410
- continue;
411
- }
412
- if (/[a-zA-Z_@:]/.test(source[i])) {
413
- const pos2 = i;
414
- while (i < len && /[a-zA-Z0-9_\-:.]/.test(source[i])) i++;
415
- tokens.push({ type: "attribute", start: pos2, end: i });
416
- while (i < len && (source[i] === " " || source[i] === " ")) i++;
417
- if (i < len && source[i] === "=") {
418
- tokens.push({ type: "operator", start: i, end: i + 1 });
419
- i++;
420
- while (i < len && (source[i] === " " || source[i] === " ")) i++;
421
- if (i < len && (source[i] === '"' || source[i] === "'")) {
422
- const quote = source[i];
423
- const pos3 = i;
424
- i++;
425
- while (i < len && source[i] !== quote) i++;
426
- i++;
427
- tokens.push({ type: "attributeValue", start: pos3, end: i });
428
- } else if (i < len && /[^\s>]/.test(source[i])) {
429
- const pos3 = i;
430
- while (i < len && /[^\s>]/.test(source[i])) i++;
431
- tokens.push({ type: "attributeValue", start: pos3, end: i });
432
- }
433
- }
434
- continue;
435
- }
436
- i++;
437
- }
438
- if (i < len && source[i] === "/") {
439
- tokens.push({ type: "punctuation", start: i, end: i + 1 });
440
- i++;
441
- }
442
- if (i < len && source[i] === ">") {
443
- tokens.push({ type: "punctuation", start: i, end: i + 1 });
444
- i++;
445
- }
446
- continue;
447
- }
448
- const pos = i;
449
- while (i < len && source[i] !== "<") i++;
450
- if (i > pos) {
451
- tokens.push({ type: "plain", start: pos, end: i });
452
- }
453
- }
454
- return tokens;
455
- };
456
-
457
449
  // src/engine/tokenizers/php.ts
458
450
  var PHP_KEYWORDS = /* @__PURE__ */ new Set([
459
451
  "abstract",
@@ -631,7 +623,7 @@ var tokenizePhp = (source) => {
631
623
  if (source[i] === "<" && source[i + 1] === "?") {
632
624
  if (i > htmlChunkStart) {
633
625
  const htmlChunk = source.slice(htmlChunkStart, i);
634
- const htmlTokens = tokenizeHtml(htmlChunk);
626
+ const htmlTokens = fancyFileCommons.tokenizeHtml(htmlChunk);
635
627
  for (const t of htmlTokens) {
636
628
  tokens.push({ type: t.type, start: t.start + htmlChunkStart, end: t.end + htmlChunkStart });
637
629
  }
@@ -658,7 +650,7 @@ var tokenizePhp = (source) => {
658
650
  }
659
651
  if (htmlChunkStart < len) {
660
652
  const htmlChunk = source.slice(htmlChunkStart);
661
- const htmlTokens = tokenizeHtml(htmlChunk);
653
+ const htmlTokens = fancyFileCommons.tokenizeHtml(htmlChunk);
662
654
  for (const t of htmlTokens) {
663
655
  tokens.push({ type: t.type, start: t.start + htmlChunkStart, end: t.end + htmlChunkStart });
664
656
  }
@@ -1037,11 +1029,6 @@ var tokenizeGo = (source) => {
1037
1029
  return tokens;
1038
1030
  };
1039
1031
 
1040
- // src/engine/tokenizer.ts
1041
- function tok(type, s, e) {
1042
- return { type, start: s, end: e };
1043
- }
1044
-
1045
1032
  // src/engine/tokenizers/markdown.ts
1046
1033
  function tokenizeMarkdown(source) {
1047
1034
  const tokens = [];
@@ -1053,26 +1040,26 @@ function tokenizeMarkdown(source) {
1053
1040
  const len = line.length;
1054
1041
  offset += len + 1;
1055
1042
  if (/^\s*(```|~~~)/.test(line)) {
1056
- tokens.push(tok("string", base, base + len));
1043
+ tokens.push(fancyFileCommons.tok("string", base, base + len));
1057
1044
  inFence = !inFence;
1058
1045
  continue;
1059
1046
  }
1060
1047
  if (inFence) {
1061
- tokens.push(tok("string", base, base + len));
1048
+ tokens.push(fancyFileCommons.tok("string", base, base + len));
1062
1049
  continue;
1063
1050
  }
1064
1051
  if (/^#{1,6}\s/.test(line)) {
1065
- tokens.push(tok("keyword", base, base + len));
1052
+ tokens.push(fancyFileCommons.tok("keyword", base, base + len));
1066
1053
  continue;
1067
1054
  }
1068
1055
  if (/^\s*>/.test(line)) {
1069
- tokens.push(tok("comment", base, base + len));
1056
+ tokens.push(fancyFileCommons.tok("comment", base, base + len));
1070
1057
  continue;
1071
1058
  }
1072
1059
  const list = line.match(/^(\s*)([-*+]|\d+\.)\s/);
1073
1060
  if (list) {
1074
1061
  const start = base + list[1].length;
1075
- tokens.push(tok("operator", start, start + list[2].length));
1062
+ tokens.push(fancyFileCommons.tok("operator", start, start + list[2].length));
1076
1063
  }
1077
1064
  pushInline(line, base, tokens);
1078
1065
  }
@@ -1082,7 +1069,7 @@ function pushInline(line, base, out) {
1082
1069
  if (line.length > 5e3) return;
1083
1070
  const spans = [];
1084
1071
  const add = (type, start, end) => {
1085
- spans.push(tok(type, base + start, base + end));
1072
+ spans.push(fancyFileCommons.tok(type, base + start, base + end));
1086
1073
  };
1087
1074
  let m;
1088
1075
  const code = /`[^`]+`/g;
@@ -1125,7 +1112,7 @@ registerLanguage({
1125
1112
  registerLanguage({
1126
1113
  name: "HTML",
1127
1114
  aliases: ["html", "htm"],
1128
- tokenize: tokenizeHtml
1115
+ tokenize: fancyFileCommons.tokenizeHtml
1129
1116
  });
1130
1117
  registerLanguage({
1131
1118
  name: "PHP",
@@ -1246,106 +1233,17 @@ function getTheme(name) {
1246
1233
  function getRegisteredThemes() {
1247
1234
  return Array.from(themeRegistry.keys());
1248
1235
  }
1249
-
1250
- // src/themes/light.ts
1251
1236
  registerTheme({
1252
1237
  name: "light",
1253
1238
  variant: "light",
1254
- colors: {
1255
- background: "#ffffff",
1256
- foreground: "#1e1e2e",
1257
- gutterBackground: "#f8fafc",
1258
- gutterForeground: "#94a3b8",
1259
- gutterBorder: "#e2e8f0",
1260
- activeLineBackground: "#f8fafc",
1261
- selectionBackground: "#dbeafe",
1262
- cursorColor: "#3b82f6",
1263
- keyword: "#8b5cf6",
1264
- string: "#059669",
1265
- comment: "#94a3b8",
1266
- number: "#d97706",
1267
- operator: "#0891b2",
1268
- function: "#2563eb",
1269
- type: "#d97706",
1270
- tag: "#dc2626",
1271
- attribute: "#d97706",
1272
- attributeValue: "#059669",
1273
- punctuation: "#64748b",
1274
- variable: "#1e1e2e"
1275
- }
1239
+ colors: fancyFileCommons.LIGHT_COLORS
1276
1240
  });
1277
-
1278
- // src/themes/dark.ts
1279
1241
  registerTheme({
1280
1242
  name: "dark",
1281
1243
  variant: "dark",
1282
- colors: {
1283
- background: "#18181b",
1284
- foreground: "#e4e4e7",
1285
- gutterBackground: "#18181b",
1286
- gutterForeground: "#52525b",
1287
- gutterBorder: "#27272a",
1288
- activeLineBackground: "#27272a",
1289
- selectionBackground: "#1e3a5f",
1290
- cursorColor: "#60a5fa",
1291
- keyword: "#c084fc",
1292
- string: "#34d399",
1293
- comment: "#71717a",
1294
- number: "#fbbf24",
1295
- operator: "#22d3ee",
1296
- function: "#60a5fa",
1297
- type: "#fbbf24",
1298
- tag: "#f87171",
1299
- attribute: "#fbbf24",
1300
- attributeValue: "#34d399",
1301
- punctuation: "#a1a1aa",
1302
- variable: "#e4e4e7"
1303
- }
1244
+ colors: fancyFileCommons.DARK_COLORS
1304
1245
  });
1305
1246
 
1306
- // src/engine/highlight.ts
1307
- function escapeHtml(text) {
1308
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1309
- }
1310
- var COLOR_RE = /^(#[0-9a-fA-F]{3,8}|(?:rgb|rgba|hsl|hsla)\([\d\s,.%/-]+\)|[a-zA-Z]+)$/;
1311
- function safeColor(value) {
1312
- return typeof value === "string" && COLOR_RE.test(value.trim()) ? value.trim() : "inherit";
1313
- }
1314
- function highlightCode(source, tokens, colors) {
1315
- if (tokens.length === 0) return escapeHtml(source);
1316
- const colorMap = {
1317
- keyword: colors.keyword,
1318
- string: colors.string,
1319
- comment: colors.comment,
1320
- number: colors.number,
1321
- operator: colors.operator,
1322
- function: colors.function,
1323
- type: colors.type,
1324
- tag: colors.tag,
1325
- attribute: colors.attribute,
1326
- attributeValue: colors.attributeValue,
1327
- punctuation: colors.punctuation,
1328
- variable: colors.variable,
1329
- plain: colors.foreground
1330
- };
1331
- const parts = [];
1332
- let pos = 0;
1333
- for (const token of tokens) {
1334
- if (token.start > pos) {
1335
- parts.push(escapeHtml(source.slice(pos, token.start)));
1336
- }
1337
- const text = escapeHtml(source.slice(token.start, token.end));
1338
- const color = safeColor(colorMap[token.type]);
1339
- const style = token.type === "comment" ? `color:${color};font-style:italic` : `color:${color}`;
1340
- parts.push(`<span style="${style}">${text}</span>`);
1341
- pos = token.end;
1342
- }
1343
- if (pos < source.length) {
1344
- parts.push(escapeHtml(source.slice(pos)));
1345
- }
1346
- return parts.join("");
1347
- }
1348
-
1349
1247
  // src/engine/position.ts
1350
1248
  function lineColumnToOffset(text, line, column = 1) {
1351
1249
  const lines = text.split("\n");
@@ -1413,7 +1311,7 @@ function useEditorEngine({
1413
1311
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1414
1312
  }
1415
1313
  const tokens = tokenizer(value);
1416
- return highlightCode(value, tokens, themeColors);
1314
+ return fancyFileCommons.highlightCode(value, tokens, themeColors);
1417
1315
  }, [value, tokenizer, themeColors]);
1418
1316
  const lineCount = react.useMemo(() => {
1419
1317
  let count = 1;
@@ -1617,9 +1515,14 @@ function CodeEditorRoot({
1617
1515
  minHeight,
1618
1516
  maxHeight,
1619
1517
  cursorLine,
1620
- cursorColumn
1518
+ cursorColumn,
1519
+ diffBase
1621
1520
  }) {
1622
1521
  const [currentValue, setCurrentValue] = reactFancy.useControllableState(valueProp, defaultValue, onChange);
1522
+ const diffAnnotations = react.useMemo(
1523
+ () => diffBase == null ? null : fancyFileCommons.annotateLines(diffBase, currentValue),
1524
+ [diffBase, currentValue]
1525
+ );
1623
1526
  const isDark = useDarkMode();
1624
1527
  const resolvedTheme = theme === "auto" ? isDark ? "dark" : "light" : theme;
1625
1528
  const [currentLanguage, setCurrentLanguageState] = react.useState(() => {
@@ -1698,11 +1601,17 @@ function CodeEditorRoot({
1698
1601
  selectionLength,
1699
1602
  textareaRef: engineReturn.textareaRef,
1700
1603
  placeholder,
1604
+ diffStats: diffAnnotations ? {
1605
+ added: diffAnnotations.added,
1606
+ modified: diffAnnotations.modified,
1607
+ removed: diffAnnotations.removed
1608
+ } : null,
1609
+ _diffAnnotations: diffAnnotations,
1701
1610
  _engineReturn: engineReturn,
1702
1611
  _minHeight: minHeight,
1703
1612
  _maxHeight: maxHeight
1704
1613
  }),
1705
- [engineReturn, currentValue, currentLanguage, setLanguage, resolvedTheme, readOnly, showLineNumbers, isWordWrap, tabSizeProp, cursorPosition, selectionLength, setCurrentValue, placeholder, minHeight, maxHeight]
1614
+ [engineReturn, currentValue, currentLanguage, setLanguage, resolvedTheme, readOnly, showLineNumbers, isWordWrap, tabSizeProp, cursorPosition, selectionLength, setCurrentValue, placeholder, diffAnnotations, minHeight, maxHeight]
1706
1615
  );
1707
1616
  return /* @__PURE__ */ jsxRuntime.jsx(CodeEditorContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxRuntime.jsx(
1708
1617
  "div",
@@ -1727,7 +1636,7 @@ var CodeEditor = Object.assign(CodeEditorRoot, {
1727
1636
  });
1728
1637
 
1729
1638
  // src/engine/markdown-render.ts
1730
- function escapeHtml2(s) {
1639
+ function escapeHtml(s) {
1731
1640
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1732
1641
  }
1733
1642
  function escapeAttr(s) {
@@ -1768,10 +1677,10 @@ function renderMarkdown(markdown) {
1768
1677
  i++;
1769
1678
  }
1770
1679
  i++;
1771
- out.push(`<pre><code${lang ? ` class="language-${escapeAttr(escapeHtml2(lang))}"` : ""}>${escapeHtml2(body.join("\n"))}</code></pre>`);
1680
+ out.push(`<pre><code${lang ? ` class="language-${escapeAttr(escapeHtml(lang))}"` : ""}>${escapeHtml(body.join("\n"))}</code></pre>`);
1772
1681
  continue;
1773
1682
  }
1774
- const esc = escapeHtml2(line);
1683
+ const esc = escapeHtml(line);
1775
1684
  if (/^\s*$/.test(line)) {
1776
1685
  flushPara();
1777
1686
  closeList();
@@ -1878,48 +1787,10 @@ function MarkdownEditor({
1878
1787
  );
1879
1788
  }
1880
1789
  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
1790
  function resolveFileKind({ filename, mime }) {
1920
1791
  const media = reactFancy.resolveMediaType({ mime, src: filename });
1921
1792
  if (media !== "unknown") return { kind: "media", mediaKind: media };
1922
- return { kind: "text", language: languageFromFilename(filename) };
1793
+ return { kind: "text", language: fancyFileCommons.languageFromFilename(filename) };
1923
1794
  }
1924
1795
  function FileViewer({
1925
1796
  filename,
@@ -1999,15 +1870,22 @@ function CodeEditorInputTag(props) {
1999
1870
  return /* @__PURE__ */ jsxRuntime.jsx(reactFancy.InputTag, { adapter, ...props });
2000
1871
  }
2001
1872
 
1873
+ Object.defineProperty(exports, "languageFromFilename", {
1874
+ enumerable: true,
1875
+ get: function () { return fancyFileCommons.languageFromFilename; }
1876
+ });
2002
1877
  exports.CodeEditor = CodeEditor;
2003
1878
  exports.CodeEditorInputTag = CodeEditorInputTag;
2004
1879
  exports.FileViewer = FileViewer;
2005
1880
  exports.MarkdownEditor = MarkdownEditor;
1881
+ exports.diffMarkColor = diffMarkColor;
1882
+ exports.diffRemovedColor = diffRemovedColor;
2006
1883
  exports.getLanguage = getLanguage;
2007
1884
  exports.getRegisteredLanguages = getRegisteredLanguages;
2008
1885
  exports.getRegisteredThemes = getRegisteredThemes;
2009
1886
  exports.getTheme = getTheme;
2010
- exports.languageFromFilename = languageFromFilename;
1887
+ exports.gutterDiffMark = gutterDiffMark;
1888
+ exports.hasDeletedAtEnd = hasDeletedAtEnd;
2011
1889
  exports.registerLanguage = registerLanguage;
2012
1890
  exports.registerTheme = registerTheme;
2013
1891
  exports.renderMarkdown = renderMarkdown;