@elabs-ai/components-viewer 4.0.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.
Files changed (140) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +143 -0
  3. package/dist/chunk-2NQ4RSJ3.js +18 -0
  4. package/dist/chunk-2NQ4RSJ3.js.map +1 -0
  5. package/dist/chunk-3U3MESBU.js +21 -0
  6. package/dist/chunk-3U3MESBU.js.map +1 -0
  7. package/dist/chunk-4OFQYALG.js +147 -0
  8. package/dist/chunk-4OFQYALG.js.map +1 -0
  9. package/dist/chunk-5OMONA2G.js +87 -0
  10. package/dist/chunk-5OMONA2G.js.map +1 -0
  11. package/dist/chunk-AORNMH77.js +58 -0
  12. package/dist/chunk-AORNMH77.js.map +1 -0
  13. package/dist/chunk-BCF3RWRL.js +18 -0
  14. package/dist/chunk-BCF3RWRL.js.map +1 -0
  15. package/dist/chunk-FELIAGKI.js +22 -0
  16. package/dist/chunk-FELIAGKI.js.map +1 -0
  17. package/dist/chunk-GGR7BNW4.js +18 -0
  18. package/dist/chunk-GGR7BNW4.js.map +1 -0
  19. package/dist/chunk-H3OAON3D.js +22 -0
  20. package/dist/chunk-H3OAON3D.js.map +1 -0
  21. package/dist/chunk-HMG2ERXH.js +17 -0
  22. package/dist/chunk-HMG2ERXH.js.map +1 -0
  23. package/dist/chunk-KOMDRWOU.js +21 -0
  24. package/dist/chunk-KOMDRWOU.js.map +1 -0
  25. package/dist/chunk-NMA57QZ7.js +117 -0
  26. package/dist/chunk-NMA57QZ7.js.map +1 -0
  27. package/dist/chunk-RGWDIQJR.js +26 -0
  28. package/dist/chunk-RGWDIQJR.js.map +1 -0
  29. package/dist/chunk-SLXRUYT3.js +91 -0
  30. package/dist/chunk-SLXRUYT3.js.map +1 -0
  31. package/dist/chunk-UJIJECEF.js +21 -0
  32. package/dist/chunk-UJIJECEF.js.map +1 -0
  33. package/dist/chunk-UL43NGUG.js +68 -0
  34. package/dist/chunk-UL43NGUG.js.map +1 -0
  35. package/dist/chunk-VO2273Z2.js +55 -0
  36. package/dist/chunk-VO2273Z2.js.map +1 -0
  37. package/dist/code-adapter-ADZ4UOGN.js +226 -0
  38. package/dist/code-adapter-ADZ4UOGN.js.map +1 -0
  39. package/dist/csv-adapter-6VU3FFVU.js +100 -0
  40. package/dist/csv-adapter-6VU3FFVU.js.map +1 -0
  41. package/dist/docx-adapter-5CQDHWTD.js +383 -0
  42. package/dist/docx-adapter-5CQDHWTD.js.map +1 -0
  43. package/dist/image-adapter-WOHZR24J.js +132 -0
  44. package/dist/image-adapter-WOHZR24J.js.map +1 -0
  45. package/dist/index.d.ts +1353 -0
  46. package/dist/index.js +1364 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/json-adapter-ZUW5GQHE.js +90 -0
  49. package/dist/json-adapter-ZUW5GQHE.js.map +1 -0
  50. package/dist/markdown-adapter-YC6WTBS4.js +214 -0
  51. package/dist/markdown-adapter-YC6WTBS4.js.map +1 -0
  52. package/dist/media-adapter-MCTB4GBH.js +78 -0
  53. package/dist/media-adapter-MCTB4GBH.js.map +1 -0
  54. package/dist/pdf-adapter-5PMKEXUD.js +424 -0
  55. package/dist/pdf-adapter-5PMKEXUD.js.map +1 -0
  56. package/dist/pptx-adapter-6GEQLS2Z.js +351 -0
  57. package/dist/pptx-adapter-6GEQLS2Z.js.map +1 -0
  58. package/dist/text-adapter-NFNWB5W3.js +81 -0
  59. package/dist/text-adapter-NFNWB5W3.js.map +1 -0
  60. package/dist/xlsx-adapter-CM2Y6AKQ.js +167 -0
  61. package/dist/xlsx-adapter-CM2Y6AKQ.js.map +1 -0
  62. package/package.json +103 -0
  63. package/src/adapters/code/code-adapter.test.tsx +221 -0
  64. package/src/adapters/code/code-adapter.tsx +272 -0
  65. package/src/adapters/code/code-language.test.ts +45 -0
  66. package/src/adapters/code/code-language.ts +100 -0
  67. package/src/adapters/code/code-manifest.ts +19 -0
  68. package/src/adapters/code/code-theme.test.ts +46 -0
  69. package/src/adapters/code/code-theme.ts +99 -0
  70. package/src/adapters/csv/csv-adapter.test.tsx +169 -0
  71. package/src/adapters/csv/csv-adapter.tsx +146 -0
  72. package/src/adapters/csv/csv-manifest.ts +20 -0
  73. package/src/adapters/docx/docx-adapter.test.tsx +192 -0
  74. package/src/adapters/docx/docx-adapter.tsx +328 -0
  75. package/src/adapters/docx/docx-manifest.ts +18 -0
  76. package/src/adapters/docx/docx-model.test.ts +181 -0
  77. package/src/adapters/docx/docx-model.ts +294 -0
  78. package/src/adapters/image/image-adapter.test.tsx +141 -0
  79. package/src/adapters/image/image-adapter.tsx +191 -0
  80. package/src/adapters/image/image-manifest.ts +15 -0
  81. package/src/adapters/index.ts +64 -0
  82. package/src/adapters/json/json-adapter.test.tsx +77 -0
  83. package/src/adapters/json/json-adapter.tsx +141 -0
  84. package/src/adapters/json/json-manifest.ts +11 -0
  85. package/src/adapters/markdown/markdown-adapter.test.tsx +228 -0
  86. package/src/adapters/markdown/markdown-adapter.tsx +353 -0
  87. package/src/adapters/markdown/markdown-manifest.ts +21 -0
  88. package/src/adapters/markdown/markdown-marks.test.ts +85 -0
  89. package/src/adapters/markdown/markdown-marks.ts +119 -0
  90. package/src/adapters/media/media-adapter.test.tsx +79 -0
  91. package/src/adapters/media/media-adapter.tsx +123 -0
  92. package/src/adapters/media/media-manifest.ts +17 -0
  93. package/src/adapters/office-fixture.ts +239 -0
  94. package/src/adapters/pdf/pdf-adapter.test.tsx +489 -0
  95. package/src/adapters/pdf/pdf-adapter.tsx +599 -0
  96. package/src/adapters/pdf/pdf-engine.ts +171 -0
  97. package/src/adapters/pdf/pdf-fixture.ts +27 -0
  98. package/src/adapters/pdf/pdf-manifest.ts +30 -0
  99. package/src/adapters/pdf/pdf-text.test.ts +171 -0
  100. package/src/adapters/pdf/pdf-text.ts +202 -0
  101. package/src/adapters/pptx/pptx-adapter.test.tsx +241 -0
  102. package/src/adapters/pptx/pptx-adapter.tsx +341 -0
  103. package/src/adapters/pptx/pptx-manifest.ts +19 -0
  104. package/src/adapters/pptx/pptx-model.test.ts +219 -0
  105. package/src/adapters/pptx/pptx-model.ts +248 -0
  106. package/src/adapters/text/text-adapter.test.tsx +125 -0
  107. package/src/adapters/text/text-adapter.tsx +117 -0
  108. package/src/adapters/text/text-manifest.ts +19 -0
  109. package/src/adapters/xlsx/xlsx-adapter.test.tsx +159 -0
  110. package/src/adapters/xlsx/xlsx-adapter.tsx +250 -0
  111. package/src/adapters/xlsx/xlsx-manifest.ts +26 -0
  112. package/src/components/grid-text.test.ts +68 -0
  113. package/src/components/grid-text.ts +77 -0
  114. package/src/components/marked-text.tsx +36 -0
  115. package/src/components/sheet-table.tsx +142 -0
  116. package/src/core/errors.ts +120 -0
  117. package/src/core/highlight-marks.test.ts +95 -0
  118. package/src/core/highlight-marks.ts +95 -0
  119. package/src/core/highlight-resolve.test.ts +208 -0
  120. package/src/core/highlight-resolve.ts +191 -0
  121. package/src/core/highlight.ts +125 -0
  122. package/src/core/registry.test.ts +172 -0
  123. package/src/core/registry.ts +188 -0
  124. package/src/core/scroll-host.ts +42 -0
  125. package/src/core/text-index.test.ts +95 -0
  126. package/src/core/text-index.ts +159 -0
  127. package/src/core/types.ts +285 -0
  128. package/src/core/use-highlight-scroll.ts +59 -0
  129. package/src/core/use-page-control.ts +47 -0
  130. package/src/core/use-paged-scroll.ts +194 -0
  131. package/src/core/use-viewport-size.ts +54 -0
  132. package/src/core/zoom.ts +56 -0
  133. package/src/file-viewer/file-viewer-context.tsx +239 -0
  134. package/src/file-viewer/file-viewer-find.tsx +204 -0
  135. package/src/file-viewer/file-viewer-pager.tsx +120 -0
  136. package/src/file-viewer/file-viewer-zoom.tsx +166 -0
  137. package/src/file-viewer/file-viewer.stories.tsx +947 -0
  138. package/src/file-viewer/file-viewer.test.tsx +947 -0
  139. package/src/file-viewer/file-viewer.tsx +1143 -0
  140. package/src/index.ts +157 -0
@@ -0,0 +1,226 @@
1
+ "use client";
2
+ "use client";
3
+ import {
4
+ codeManifest,
5
+ languageFor
6
+ } from "./chunk-5OMONA2G.js";
7
+ import {
8
+ localizeRanges,
9
+ toMarkRanges,
10
+ useScrollActiveHighlightIntoView
11
+ } from "./chunk-VO2273Z2.js";
12
+ import {
13
+ toViewerError
14
+ } from "./chunk-AORNMH77.js";
15
+
16
+ // src/adapters/code/code-adapter.tsx
17
+ import { MatchHighlight, cn, StatePanel, useLocale } from "@elabs-ai/components-ui";
18
+ import { Fragment, useMemo, useRef } from "react";
19
+
20
+ // src/adapters/code/code-theme.ts
21
+ var SCOPE_COLORS = [
22
+ {
23
+ scopes: ["comment", "punctuation.definition.comment"],
24
+ token: "--code-comment",
25
+ italic: true
26
+ },
27
+ { scopes: ["string", "string.quoted", "punctuation.definition.string"], token: "--code-string" },
28
+ {
29
+ scopes: ["constant", "constant.language", "constant.character", "support.constant"],
30
+ token: "--code-constant"
31
+ },
32
+ { scopes: ["constant.numeric", "keyword.other.unit"], token: "--code-number" },
33
+ {
34
+ scopes: ["keyword", "keyword.control", "keyword.operator", "storage", "storage.type"],
35
+ token: "--code-keyword"
36
+ },
37
+ {
38
+ scopes: ["entity.name.function", "support.function", "meta.function-call"],
39
+ token: "--code-function"
40
+ },
41
+ {
42
+ scopes: ["entity.name.type", "entity.name.class", "support.type", "support.class"],
43
+ token: "--code-type"
44
+ },
45
+ { scopes: ["entity.name.tag", "punctuation.definition.tag"], token: "--code-tag" },
46
+ { scopes: ["variable", "support.variable", "meta.object-literal.key"], token: "--code-property" },
47
+ // Same colour as a property, but the one other scope that leans. Its own rule,
48
+ // for the reason in the docblock above.
49
+ { scopes: ["entity.other.attribute-name"], token: "--code-property", italic: true }
50
+ ];
51
+ var codeViewerTheme = {
52
+ name: "brand-ui-viewer",
53
+ // Shiki uses `type` only to pick its own light/dark defaults, which this theme
54
+ // overrides wholesale. It is `css` — neither, resolved at paint time.
55
+ type: "dark",
56
+ colors: {
57
+ // The renderer owns the surface; the theme must not paint over it.
58
+ "editor.background": "transparent",
59
+ "editor.foreground": "var(--code-foreground)"
60
+ },
61
+ fg: "var(--code-foreground)",
62
+ bg: "transparent",
63
+ settings: [
64
+ { settings: { foreground: "var(--code-foreground)" } },
65
+ ...SCOPE_COLORS.map(({ scopes, token, italic }) => ({
66
+ scope: scopes,
67
+ settings: {
68
+ foreground: `var(${token})`,
69
+ ...italic ? { fontStyle: "italic" } : void 0
70
+ }
71
+ }))
72
+ ]
73
+ };
74
+
75
+ // src/adapters/code/code-adapter.tsx
76
+ import { jsx, jsxs } from "react/jsx-runtime";
77
+ var CODE_CHARACTER_LIMIT = 4e5;
78
+ var hasFontFlag = (fontStyle, flag) => ((fontStyle ?? 0) & flag) === flag;
79
+ var highlighters = /* @__PURE__ */ new Map();
80
+ function getHighlighter(language) {
81
+ let pending = highlighters.get(language);
82
+ if (!pending) {
83
+ pending = (async () => {
84
+ const { createHighlighter } = await import("shiki");
85
+ const highlighter = await createHighlighter({
86
+ themes: [codeViewerTheme],
87
+ langs: [language]
88
+ });
89
+ return {
90
+ tokenize: (code) => highlighter.codeToTokens(code, {
91
+ lang: language,
92
+ theme: codeViewerTheme.name
93
+ }).tokens
94
+ };
95
+ })().catch((error) => {
96
+ highlighters.delete(language);
97
+ throw error;
98
+ });
99
+ highlighters.set(language, pending);
100
+ }
101
+ return pending;
102
+ }
103
+ function plainLines(text) {
104
+ return text.split("\n").map((line) => line.length > 0 ? [{ text: line }] : []);
105
+ }
106
+ var CodeAdapter = class {
107
+ async load(source, context) {
108
+ let raw;
109
+ try {
110
+ raw = await source.text(context.signal);
111
+ } catch (error) {
112
+ throw toViewerError(error, "read-failed", { fileName: source.name });
113
+ }
114
+ const truncated = raw.length > CODE_CHARACTER_LIMIT;
115
+ const text = truncated ? raw.slice(0, CODE_CHARACTER_LIMIT) : raw;
116
+ const totalCharacters = truncated ? raw.length : void 0;
117
+ const textTruncated = truncated || void 0;
118
+ const language = languageFor(source.name);
119
+ if (!language) {
120
+ return { kind: "code", lines: plainLines(text), text, totalCharacters, textTruncated };
121
+ }
122
+ const highlighter = await getHighlighter(language);
123
+ if (context.signal?.aborted) {
124
+ return {
125
+ kind: "code",
126
+ language,
127
+ lines: plainLines(text),
128
+ text,
129
+ totalCharacters,
130
+ textTruncated
131
+ };
132
+ }
133
+ const lines = highlighter.tokenize(text).map(
134
+ (line) => line.map((token) => ({
135
+ text: token.content,
136
+ color: token.color,
137
+ italic: hasFontFlag(token.fontStyle, 1) || void 0,
138
+ bold: hasFontFlag(token.fontStyle, 2) || void 0,
139
+ underline: hasFontFlag(token.fontStyle, 4) || void 0
140
+ }))
141
+ );
142
+ return { kind: "code", language, lines, text, totalCharacters, textTruncated };
143
+ }
144
+ };
145
+ function lineStartsIn(text) {
146
+ const starts = [0];
147
+ for (let i = 0; i < text.length; i++) {
148
+ if (text[i] === "\n") starts.push(i + 1);
149
+ }
150
+ return starts;
151
+ }
152
+ function CodeRenderer({
153
+ document: doc,
154
+ className,
155
+ highlights,
156
+ activeHighlightId
157
+ }) {
158
+ const code = doc;
159
+ const { t, formatNumber } = useLocale();
160
+ const container = useRef(null);
161
+ const lineStarts = useMemo(() => lineStartsIn(code.text), [code.text]);
162
+ const marks = useMemo(
163
+ () => toMarkRanges(highlights, code.text.length),
164
+ [highlights, code.text.length]
165
+ );
166
+ useScrollActiveHighlightIntoView(container, activeHighlightId);
167
+ if (code.text.length === 0) {
168
+ return /* @__PURE__ */ jsx(StatePanel, { kind: "empty", title: t("viewer.file.empty"), className });
169
+ }
170
+ return (
171
+ // No `overflow-auto` here: `FileViewerContent` is the scroll boundary, and
172
+ // nesting a second one clips the last line above the outer pane's padding.
173
+ /* @__PURE__ */ jsxs("div", { ref: container, className: cn("flex flex-col gap-2", className), children: [
174
+ code.totalCharacters !== void 0 && // Not an error — the file is fine, we are showing part of it.
175
+ /* @__PURE__ */ jsx("p", { role: "status", className: "text-meta text-muted-foreground", children: t("viewer.text.truncated", {
176
+ shown: formatNumber(code.text.length),
177
+ total: formatNumber(code.totalCharacters)
178
+ }) }),
179
+ /* @__PURE__ */ jsx("pre", { className: "text-code text-code-foreground font-mono", children: /* @__PURE__ */ jsx("code", { className: "grid grid-cols-[auto_1fr] gap-x-4", children: code.lines.map((tokens, lineIndex) => {
180
+ let offset = lineStarts[lineIndex] ?? code.text.length;
181
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
182
+ /* @__PURE__ */ jsx(
183
+ "span",
184
+ {
185
+ "aria-hidden": "true",
186
+ className: "text-muted-foreground text-right tabular-nums select-none",
187
+ children: lineIndex + 1
188
+ }
189
+ ),
190
+ /* @__PURE__ */ jsx("span", { className: "break-words whitespace-pre-wrap", children: tokens.map((token, tokenIndex) => {
191
+ const start = offset;
192
+ offset += token.text.length;
193
+ const local = localizeRanges(marks, start, offset);
194
+ const style = {
195
+ color: token.color,
196
+ fontStyle: token.italic ? "italic" : void 0,
197
+ fontWeight: token.bold ? "bold" : void 0,
198
+ textDecoration: token.underline ? "underline" : void 0
199
+ };
200
+ return local.ranges.length === 0 ? /* @__PURE__ */ jsx("span", { style, children: token.text }, tokenIndex) : /* @__PURE__ */ jsx(
201
+ MatchHighlight,
202
+ {
203
+ style,
204
+ text: token.text,
205
+ ranges: local.ranges,
206
+ activeIndex: local.activeIndex
207
+ },
208
+ tokenIndex
209
+ );
210
+ }) })
211
+ ] }, lineIndex);
212
+ }) }) })
213
+ ] })
214
+ );
215
+ }
216
+ var adapterModule = {
217
+ manifest: codeManifest,
218
+ create: () => new CodeAdapter(),
219
+ Renderer: CodeRenderer
220
+ };
221
+ var code_adapter_default = adapterModule;
222
+ export {
223
+ CODE_CHARACTER_LIMIT,
224
+ code_adapter_default as default
225
+ };
226
+ //# sourceMappingURL=code-adapter-ADZ4UOGN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/adapters/code/code-adapter.tsx","../src/adapters/code/code-theme.ts"],"sourcesContent":["\"use client\";\n\n/**\n * Code adapter — source files, highlighted with Shiki and coloured by tokens.\n *\n * Highlighting happens at LOAD, not at render: `load()` returns lines of plain\n * `{ text, color }` tokens, so the renderer draws data like every other adapter\n * and Shiki never reaches the render path. The colours are `var(--code-*)`\n * references (see `code-theme.ts`), which is why a theme switch recolours the\n * document without re-tokenizing it.\n *\n * The plain-text adapter is still the backstop. This one claims extensions it\n * has a grammar for; anything else — a log, a `.env`, no extension at all —\n * falls through to text, and so does every file when Shiki is not installed.\n */\n\nimport { MatchHighlight, cn, StatePanel, useLocale } from \"@elabs-ai/components-ui\";\nimport type { ResolvedFileSource } from \"@elabs-ai/components-ui\";\nimport { Fragment, useMemo, useRef } from \"react\";\nimport type { BundledLanguage, ThemedToken } from \"shiki\";\n\nimport { toViewerError } from \"../../core/errors\";\nimport { localizeRanges, toMarkRanges } from \"../../core/highlight-marks\";\nimport { useScrollActiveHighlightIntoView } from \"../../core/use-highlight-scroll\";\nimport type {\n AdapterDocument,\n AdapterLoadContext,\n AdapterModule,\n AdapterRendererProps,\n FileAdapter,\n} from \"../../core/types\";\nimport { languageFor } from \"./code-language\";\nimport { codeManifest } from \"./code-manifest\";\nimport { codeViewerTheme } from \"./code-theme\";\n\n/**\n * Characters kept before truncation. Well below the plain-text limit because\n * this text is also TOKENIZED — a grammar pass over multiple megabytes blocks\n * the main thread, and no one reads a 400k-character file in a preview pane.\n */\nexport const CODE_CHARACTER_LIMIT = 400_000;\n\n/** One highlighted run. `color` is always a `var(--code-*)` reference. */\nexport interface CodeToken {\n text: string;\n color?: string;\n italic?: boolean;\n bold?: boolean;\n underline?: boolean;\n}\n\nexport interface CodeDocument extends AdapterDocument {\n kind: \"code\";\n /** Shiki language id, or `undefined` when the file rendered unhighlighted. */\n language?: string;\n /** One entry per line; a blank line is an empty array. */\n lines: CodeToken[][];\n text: string;\n /** Total characters in the file, when more than what is shown. */\n totalCharacters?: number;\n}\n\n// Shiki encodes font style as bitflags: 1 = italic, 2 = bold, 4 = underline.\nconst hasFontFlag = (fontStyle: number | undefined, flag: number) =>\n ((fontStyle ?? 0) & flag) === flag;\n\n/**\n * Highlighters, cached per language for the lifetime of the page.\n *\n * Not per-document state, so it deliberately outlives the adapter instance:\n * building one loads a grammar and the WASM engine, and paging through twenty\n * TypeScript files in a file tree should pay for that once.\n */\nconst highlighters = new Map<string, Promise<{ tokenize(code: string): ThemedToken[][] }>>();\n\nfunction getHighlighter(language: BundledLanguage) {\n let pending = highlighters.get(language);\n if (!pending) {\n pending = (async () => {\n // Dynamic: the ONLY edge to the optional peer (heavy-deps:check).\n const { createHighlighter } = await import(\"shiki\");\n const highlighter = await createHighlighter({\n themes: [codeViewerTheme],\n langs: [language],\n });\n return {\n tokenize: (code: string) =>\n highlighter.codeToTokens(code, {\n lang: language,\n theme: codeViewerTheme.name as string,\n }).tokens,\n };\n })().catch((error: unknown) => {\n // Not cached: a peer installed later, or a transient chunk-load failure,\n // must be retryable without a page reload.\n highlighters.delete(language);\n throw error;\n });\n highlighters.set(language, pending);\n }\n return pending;\n}\n\n/** Split unhighlighted text into the same line/token shape. */\nfunction plainLines(text: string): CodeToken[][] {\n return text.split(\"\\n\").map((line) => (line.length > 0 ? [{ text: line }] : []));\n}\n\nclass CodeAdapter implements FileAdapter {\n async load(source: ResolvedFileSource, context: AdapterLoadContext): Promise<CodeDocument> {\n let raw: string;\n try {\n raw = await source.text(context.signal);\n } catch (error) {\n throw toViewerError(error, \"read-failed\", { fileName: source.name });\n }\n\n const truncated = raw.length > CODE_CHARACTER_LIMIT;\n const text = truncated ? raw.slice(0, CODE_CHARACTER_LIMIT) : raw;\n const totalCharacters = truncated ? raw.length : undefined;\n // So a passage the shell cannot find reads as \"past the part we previewed\"\n // rather than \"not in this document\".\n const textTruncated = truncated || undefined;\n const language = languageFor(source.name);\n\n // No grammar for this name — still a perfectly readable file.\n if (!language) {\n return { kind: \"code\", lines: plainLines(text), text, totalCharacters, textTruncated };\n }\n\n const highlighter = await getHighlighter(language);\n if (context.signal?.aborted) {\n return {\n kind: \"code\",\n language,\n lines: plainLines(text),\n text,\n totalCharacters,\n textTruncated,\n };\n }\n\n const lines = highlighter.tokenize(text).map((line) =>\n line.map((token) => ({\n text: token.content,\n color: token.color,\n italic: hasFontFlag(token.fontStyle, 1) || undefined,\n bold: hasFontFlag(token.fontStyle, 2) || undefined,\n underline: hasFontFlag(token.fontStyle, 4) || undefined,\n })),\n );\n\n return { kind: \"code\", language, lines, text, totalCharacters, textTruncated };\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* Renderer */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Where each rendered line begins in `document.text`.\n *\n * Derived from the TEXT, not from the token lines: the text is the address space\n * every highlight is expressed in, so counting its newlines is the only mapping\n * that cannot drift from what a caller asked for. The two agree by construction\n * — the tokenizer splits on the same newlines — and `code-adapter.test.tsx`\n * locks that.\n */\nfunction lineStartsIn(text: string): number[] {\n const starts = [0];\n for (let i = 0; i < text.length; i++) {\n if (text[i] === \"\\n\") starts.push(i + 1);\n }\n return starts;\n}\n\nfunction CodeRenderer({\n document: doc,\n className,\n highlights,\n activeHighlightId,\n}: AdapterRendererProps) {\n const code = doc as CodeDocument;\n const { t, formatNumber } = useLocale();\n const container = useRef<HTMLDivElement>(null);\n const lineStarts = useMemo(() => lineStartsIn(code.text), [code.text]);\n const marks = useMemo(\n () => toMarkRanges(highlights, code.text.length),\n [highlights, code.text.length],\n );\n useScrollActiveHighlightIntoView(container, activeHighlightId);\n\n if (code.text.length === 0) {\n return <StatePanel kind=\"empty\" title={t(\"viewer.file.empty\")} className={className} />;\n }\n\n return (\n // No `overflow-auto` here: `FileViewerContent` is the scroll boundary, and\n // nesting a second one clips the last line above the outer pane's padding.\n <div ref={container} className={cn(\"flex flex-col gap-2\", className)}>\n {code.totalCharacters !== undefined && (\n // Not an error — the file is fine, we are showing part of it.\n <p role=\"status\" className=\"text-meta text-muted-foreground\">\n {t(\"viewer.text.truncated\", {\n shown: formatNumber(code.text.length),\n total: formatNumber(code.totalCharacters),\n })}\n </p>\n )}\n <pre className=\"text-code text-code-foreground font-mono\">\n <code className=\"grid grid-cols-[auto_1fr] gap-x-4\">\n {code.lines.map((tokens, lineIndex) => {\n let offset = lineStarts[lineIndex] ?? code.text.length;\n return (\n <Fragment key={lineIndex}>\n {/* Decorative: the numbers are a reading aid, and keeping them out\n of the accessibility tree also keeps them out of a copy. Not\n `formatNumber` — a line gutter is an ordinal, so a thousands\n separator at line 1,000 would be wrong in every locale. */}\n <span\n aria-hidden=\"true\"\n className=\"text-muted-foreground text-right tabular-nums select-none\"\n >\n {lineIndex + 1}\n </span>\n <span className=\"break-words whitespace-pre-wrap\">\n {tokens.map((token, tokenIndex) => {\n const start = offset;\n offset += token.text.length;\n // Syntax colour is inline `var(--code-*)` (see the module\n // header), and a mark's plate would fight it — so the mark\n // layer only wraps the tokens it actually touches, and every\n // other token keeps rendering exactly as before.\n const local = localizeRanges(marks, start, offset);\n const style = {\n color: token.color,\n fontStyle: token.italic ? \"italic\" : undefined,\n fontWeight: token.bold ? \"bold\" : undefined,\n textDecoration: token.underline ? \"underline\" : undefined,\n };\n return local.ranges.length === 0 ? (\n <span key={tokenIndex} style={style}>\n {token.text}\n </span>\n ) : (\n <MatchHighlight\n key={tokenIndex}\n style={style}\n text={token.text}\n ranges={local.ranges}\n activeIndex={local.activeIndex}\n />\n );\n })}\n </span>\n </Fragment>\n );\n })}\n </code>\n </pre>\n </div>\n );\n}\n\nconst adapterModule: AdapterModule = {\n manifest: codeManifest,\n create: () => new CodeAdapter(),\n Renderer: CodeRenderer,\n};\n\nexport default adapterModule;\n","/**\n * The Shiki theme the code adapter tokenizes with — brand tokens, not a\n * recoloured `github-light`.\n *\n * Every colour here is a `var(--code-*)` reference rather than a literal, which\n * is what makes ONE theme correct in EVERY brand theme: the active `data-theme`\n * block decides what `--code-keyword` resolves to, so a runtime theme switch\n * recolours already-tokenized code with no re-highlight, no `MutationObserver`\n * and no cache to invalidate. It is the same technique Shiki ships as\n * `createCssVariablesTheme`, written out by hand for one reason: that helper\n * maps a fixed, smaller scope set and cannot reach `--code-type` or\n * `--code-tag`, which this design system does define.\n *\n * `@elabs-ai/components-ai`'s `CodeBlock` resolves the same tokens\n * the other way round (oklch → hex, re-derived per theme change) because\n * `@streamdown/code` freezes its themes at import time and cannot take a live\n * `var()`. Nothing here needs that, so nothing here pays for it.\n *\n * Type-only import: this module is pure data and holds no edge to the engine.\n */\n\nimport type { ThemeRegistrationRaw } from \"shiki\";\n\n/**\n * TextMate scopes → brand code tokens.\n *\n * Ordered coarse-to-fine, because Shiki resolves the LAST matching rule: the\n * broad `constant` rule has to come before `constant.numeric` or every number\n * would read as a constant.\n *\n * `italic` is a convention, not a colour, so it is theme-safe — but it is\n * declared PER RULE and never inferred from the scope list. A rule's styling\n * applies to every scope in its array, so a lone italic scope sharing an array\n * with ordinary ones italicises all of them: bundling `entity.other.attribute-name`\n * with `variable` slanted every plain identifier in the file, which is the most\n * common token class there is. Give an italic scope its own entry.\n */\nconst SCOPE_COLORS: ReadonlyArray<{\n readonly scopes: string[];\n readonly token: string;\n readonly italic?: boolean;\n}> = [\n {\n scopes: [\"comment\", \"punctuation.definition.comment\"],\n token: \"--code-comment\",\n italic: true,\n },\n { scopes: [\"string\", \"string.quoted\", \"punctuation.definition.string\"], token: \"--code-string\" },\n {\n scopes: [\"constant\", \"constant.language\", \"constant.character\", \"support.constant\"],\n token: \"--code-constant\",\n },\n { scopes: [\"constant.numeric\", \"keyword.other.unit\"], token: \"--code-number\" },\n {\n scopes: [\"keyword\", \"keyword.control\", \"keyword.operator\", \"storage\", \"storage.type\"],\n token: \"--code-keyword\",\n },\n {\n scopes: [\"entity.name.function\", \"support.function\", \"meta.function-call\"],\n token: \"--code-function\",\n },\n {\n scopes: [\"entity.name.type\", \"entity.name.class\", \"support.type\", \"support.class\"],\n token: \"--code-type\",\n },\n { scopes: [\"entity.name.tag\", \"punctuation.definition.tag\"], token: \"--code-tag\" },\n { scopes: [\"variable\", \"support.variable\", \"meta.object-literal.key\"], token: \"--code-property\" },\n // Same colour as a property, but the one other scope that leans. Its own rule,\n // for the reason in the docblock above.\n { scopes: [\"entity.other.attribute-name\"], token: \"--code-property\", italic: true },\n];\n\n/**\n * The theme, built once at module load. Shiki treats a theme object as opaque\n * data, so one instance is safely shared by every highlighter.\n */\nexport const codeViewerTheme: ThemeRegistrationRaw = {\n name: \"brand-ui-viewer\",\n // Shiki uses `type` only to pick its own light/dark defaults, which this theme\n // overrides wholesale. It is `css` — neither, resolved at paint time.\n type: \"dark\",\n colors: {\n // The renderer owns the surface; the theme must not paint over it.\n \"editor.background\": \"transparent\",\n \"editor.foreground\": \"var(--code-foreground)\",\n },\n fg: \"var(--code-foreground)\",\n bg: \"transparent\",\n settings: [\n { settings: { foreground: \"var(--code-foreground)\" } },\n ...SCOPE_COLORS.map(({ scopes, token, italic }) => ({\n scope: scopes,\n settings: {\n foreground: `var(${token})`,\n ...(italic ? { fontStyle: \"italic\" } : undefined),\n },\n })),\n ],\n};\n"],"mappings":"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","names":[]}
@@ -0,0 +1,100 @@
1
+ "use client";
2
+ "use client";
3
+ import {
4
+ csvManifest
5
+ } from "./chunk-3U3MESBU.js";
6
+ import {
7
+ SheetTable,
8
+ gridToText
9
+ } from "./chunk-NMA57QZ7.js";
10
+ import "./chunk-2NQ4RSJ3.js";
11
+ import "./chunk-UL43NGUG.js";
12
+ import {
13
+ toMarkRanges
14
+ } from "./chunk-VO2273Z2.js";
15
+ import {
16
+ ViewerError,
17
+ toViewerError
18
+ } from "./chunk-AORNMH77.js";
19
+
20
+ // src/adapters/csv/csv-adapter.tsx
21
+ import { useMemo } from "react";
22
+ import { jsx } from "react/jsx-runtime";
23
+ var CSV_ROW_LIMIT = 5e3;
24
+ var CsvAdapter = class {
25
+ async load(source, context) {
26
+ let text;
27
+ try {
28
+ text = await source.text(context.signal);
29
+ } catch (error) {
30
+ throw toViewerError(error, "read-failed", { fileName: source.name });
31
+ }
32
+ const Papa = await import("papaparse");
33
+ const parsed = Papa.parse(text, {
34
+ skipEmptyLines: "greedy",
35
+ // Let Papa detect `,` vs `\t` vs `;` rather than trusting the extension —
36
+ // a .csv exported by a European locale is very often semicolon-delimited.
37
+ delimiter: ""
38
+ });
39
+ if (parsed.data.length === 0 && parsed.errors.length > 0) {
40
+ throw new ViewerError(
41
+ "parse-failed",
42
+ parsed.errors[0]?.message ?? "Could not parse the file.",
43
+ {
44
+ fileName: source.name
45
+ }
46
+ );
47
+ }
48
+ const [header = [], ...body] = parsed.data;
49
+ const rows = body.length <= CSV_ROW_LIMIT ? body : body.slice(0, CSV_ROW_LIMIT);
50
+ const textIndex = gridToText([{ columns: header, rows }]);
51
+ return {
52
+ kind: "csv",
53
+ columns: header,
54
+ rows,
55
+ ...body.length > CSV_ROW_LIMIT ? { totalRows: body.length } : {},
56
+ text: textIndex.text,
57
+ textIndex
58
+ };
59
+ }
60
+ };
61
+ function CsvRenderer({
62
+ document: doc,
63
+ className,
64
+ highlights,
65
+ activeHighlightId
66
+ }) {
67
+ const csv = doc;
68
+ const marks = useMemo(
69
+ () => toMarkRanges(highlights, csv.text?.length ?? 0),
70
+ [highlights, csv.text]
71
+ );
72
+ const rowStart = useMemo(() => {
73
+ const starts = /* @__PURE__ */ new Map();
74
+ for (const span of csv.textIndex?.spans ?? []) starts.set(span.ref.row, span.start);
75
+ return (row) => starts.get(row);
76
+ }, [csv.textIndex]);
77
+ return /* @__PURE__ */ jsx(
78
+ SheetTable,
79
+ {
80
+ columns: csv.columns,
81
+ rows: csv.rows,
82
+ totalRows: csv.totalRows,
83
+ className,
84
+ marks,
85
+ rowStart,
86
+ activeHighlightId
87
+ }
88
+ );
89
+ }
90
+ var adapterModule = {
91
+ manifest: csvManifest,
92
+ create: () => new CsvAdapter(),
93
+ Renderer: CsvRenderer
94
+ };
95
+ var csv_adapter_default = adapterModule;
96
+ export {
97
+ CSV_ROW_LIMIT,
98
+ csv_adapter_default as default
99
+ };
100
+ //# sourceMappingURL=csv-adapter-6VU3FFVU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/adapters/csv/csv-adapter.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * CSV / TSV adapter — a real parser and a real table.\n *\n * Two things this fixes at once:\n *\n * - **Correctness.** The repo's previous CSV \"parser\" was `line.split(\",\")`\n * (`packages/ai/src/asset-preview.tsx:69`), which mangles every quoted comma —\n * i.e. every address, every price list, every free-text column. Papa Parse\n * handles quoting, escapes, embedded newlines and delimiter detection.\n * - **Theming.** anyview's CSV adapter emits an HTML string with `#ddd` and\n * `#f5f5f5` baked in (`CsvAdapter.ts:184`). This one emits ROWS and hands them\n * to `@elabs-ai/components-ui`'s `Table`, so the grid is tokened and\n * re-themes with everything else.\n *\n * `papaparse` is an OPTIONAL peer, reached only through the dynamic import\n * below. A consumer who never opens a spreadsheet never installs it, and one who\n * forgot gets a \"install papaparse\" message rather than a resolution stack trace\n * (`isModuleNotFound` in `core/errors.ts`).\n */\n\nimport type { ResolvedFileSource } from \"@elabs-ai/components-ui\";\nimport { useMemo } from \"react\";\n\nimport { gridToText, type GridRef } from \"../../components/grid-text\";\nimport { SheetTable } from \"../../components/sheet-table\";\n\nimport { ViewerError, toViewerError } from \"../../core/errors\";\nimport { toMarkRanges } from \"../../core/highlight-marks\";\nimport type { TextIndex } from \"../../core/text-index\";\nimport type {\n AdapterDocument,\n AdapterLoadContext,\n AdapterModule,\n AdapterRendererProps,\n FileAdapter,\n} from \"../../core/types\";\nimport { csvManifest } from \"./csv-manifest\";\n\n/**\n * Rows rendered before truncation. A preview pane is for looking, not for\n * analysis — past this, reach for `@elabs-ai/components-data`'s\n * `DataTable`, which virtualizes.\n */\nexport const CSV_ROW_LIMIT = 5_000;\n\nexport interface CsvDocument extends AdapterDocument {\n kind: \"csv\";\n /** Header cells. Empty when the file had no usable first row. */\n columns: string[];\n /** Body rows, already capped at {@link CSV_ROW_LIMIT}. */\n rows: string[][];\n /** Total body rows in the file, when more than what is shown. */\n totalRows?: number;\n /** Which row each stretch of `text` came from. */\n textIndex?: TextIndex<GridRef>;\n}\n\nclass CsvAdapter implements FileAdapter {\n async load(source: ResolvedFileSource, context: AdapterLoadContext): Promise<CsvDocument> {\n let text: string;\n try {\n text = await source.text(context.signal);\n } catch (error) {\n throw toViewerError(error, \"read-failed\", { fileName: source.name });\n }\n\n // Dynamic: this is the ONLY edge to the optional peer. A static import here\n // would break every consumer that did not install it (heavy-deps:check).\n const Papa = await import(\"papaparse\");\n const parsed = Papa.parse<string[]>(text, {\n skipEmptyLines: \"greedy\",\n // Let Papa detect `,` vs `\\t` vs `;` rather than trusting the extension —\n // a .csv exported by a European locale is very often semicolon-delimited.\n delimiter: \"\",\n });\n\n // Papa reports recoverable problems as errors while still returning data;\n // only an empty result with errors is genuinely unreadable.\n if (parsed.data.length === 0 && parsed.errors.length > 0) {\n throw new ViewerError(\n \"parse-failed\",\n parsed.errors[0]?.message ?? \"Could not parse the file.\",\n {\n fileName: source.name,\n },\n );\n }\n\n const [header = [], ...body] = parsed.data;\n const rows = body.length <= CSV_ROW_LIMIT ? body : body.slice(0, CSV_ROW_LIMIT);\n // `text` is the PARSED grid, not the raw file (changed in this release).\n // A citation's offsets have to address what the reader can see: the raw\n // bytes carry quoting, escapes and a delimiter that may not be a comma, so\n // an offset into them lands nowhere in particular in the rendered table.\n const textIndex = gridToText([{ columns: header, rows }]);\n return {\n kind: \"csv\",\n columns: header,\n rows,\n ...(body.length > CSV_ROW_LIMIT ? { totalRows: body.length } : {}),\n text: textIndex.text,\n textIndex,\n };\n }\n}\n\nfunction CsvRenderer({\n document: doc,\n className,\n highlights,\n activeHighlightId,\n}: AdapterRendererProps) {\n const csv = doc as CsvDocument;\n const marks = useMemo(\n () => toMarkRanges(highlights, csv.text?.length ?? 0),\n [highlights, csv.text],\n );\n // One sheet, so a row's start is simply the span whose ref names that row.\n const rowStart = useMemo(() => {\n const starts = new Map<number, number>();\n for (const span of csv.textIndex?.spans ?? []) starts.set(span.ref.row, span.start);\n return (row: number) => starts.get(row);\n }, [csv.textIndex]);\n\n return (\n <SheetTable\n columns={csv.columns}\n rows={csv.rows}\n totalRows={csv.totalRows}\n className={className}\n marks={marks}\n rowStart={rowStart}\n activeHighlightId={activeHighlightId}\n />\n );\n}\n\nconst adapterModule: AdapterModule = {\n manifest: csvManifest,\n create: () => new CsvAdapter(),\n Renderer: CsvRenderer,\n};\n\nexport default adapterModule;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAuBA,SAAS,eAAe;AAwGpB;AAlFG,IAAM,gBAAgB;AAc7B,IAAM,aAAN,MAAwC;AAAA,EACtC,MAAM,KAAK,QAA4B,SAAmD;AACxF,QAAI;AACJ,QAAI;AACF,aAAO,MAAM,OAAO,KAAK,QAAQ,MAAM;AAAA,IACzC,SAAS,OAAO;AACd,YAAM,cAAc,OAAO,eAAe,EAAE,UAAU,OAAO,KAAK,CAAC;AAAA,IACrE;AAIA,UAAM,OAAO,MAAM,OAAO,WAAW;AACrC,UAAM,SAAS,KAAK,MAAgB,MAAM;AAAA,MACxC,gBAAgB;AAAA;AAAA;AAAA,MAGhB,WAAW;AAAA,IACb,CAAC;AAID,QAAI,OAAO,KAAK,WAAW,KAAK,OAAO,OAAO,SAAS,GAAG;AACxD,YAAM,IAAI;AAAA,QACR;AAAA,QACA,OAAO,OAAO,CAAC,GAAG,WAAW;AAAA,QAC7B;AAAA,UACE,UAAU,OAAO;AAAA,QACnB;AAAA,MACF;AAAA,IACF;AAEA,UAAM,CAAC,SAAS,CAAC,GAAG,GAAG,IAAI,IAAI,OAAO;AACtC,UAAM,OAAO,KAAK,UAAU,gBAAgB,OAAO,KAAK,MAAM,GAAG,aAAa;AAK9E,UAAM,YAAY,WAAW,CAAC,EAAE,SAAS,QAAQ,KAAK,CAAC,CAAC;AACxD,WAAO;AAAA,MACL,MAAM;AAAA,MACN,SAAS;AAAA,MACT;AAAA,MACA,GAAI,KAAK,SAAS,gBAAgB,EAAE,WAAW,KAAK,OAAO,IAAI,CAAC;AAAA,MAChE,MAAM,UAAU;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,YAAY;AAAA,EACnB,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA;AACF,GAAyB;AACvB,QAAM,MAAM;AACZ,QAAM,QAAQ;AAAA,IACZ,MAAM,aAAa,YAAY,IAAI,MAAM,UAAU,CAAC;AAAA,IACpD,CAAC,YAAY,IAAI,IAAI;AAAA,EACvB;AAEA,QAAM,WAAW,QAAQ,MAAM;AAC7B,UAAM,SAAS,oBAAI,IAAoB;AACvC,eAAW,QAAQ,IAAI,WAAW,SAAS,CAAC,EAAG,QAAO,IAAI,KAAK,IAAI,KAAK,KAAK,KAAK;AAClF,WAAO,CAAC,QAAgB,OAAO,IAAI,GAAG;AAAA,EACxC,GAAG,CAAC,IAAI,SAAS,CAAC;AAElB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,SAAS,IAAI;AAAA,MACb,MAAM,IAAI;AAAA,MACV,WAAW,IAAI;AAAA,MACf;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,EACF;AAEJ;AAEA,IAAM,gBAA+B;AAAA,EACnC,UAAU;AAAA,EACV,QAAQ,MAAM,IAAI,WAAW;AAAA,EAC7B,UAAU;AACZ;AAEA,IAAO,sBAAQ;","names":[]}