@mirror-physics/fractal-ui 0.2.0-next.51 → 0.2.0-next.53

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/dist/index.js CHANGED
@@ -168,7 +168,7 @@ Button.displayName = "Button";
168
168
  // src/components/CodeBlock/CodeBlock.tsx
169
169
  import { useEffect, useMemo, useRef, useState as useState2 } from "react";
170
170
  import CodeMirror from "@uiw/react-codemirror";
171
- import { HighlightStyle, foldGutter, syntaxHighlighting } from "@codemirror/language";
171
+ import { HighlightStyle, foldGutter, foldService, syntaxHighlighting } from "@codemirror/language";
172
172
  import { json } from "@codemirror/lang-json";
173
173
  import { EditorView, lineNumbers as lineNumberGutter } from "@codemirror/view";
174
174
  import { tags } from "@lezer/highlight";
@@ -197,22 +197,23 @@ var codeTheme = EditorView.theme({
197
197
  ".cm-lineNumbers .cm-gutterElement": { padding: "0 10px 0 4px" },
198
198
  ".cm-foldGutter": { width: "14px" },
199
199
  ".cm-foldGutter .cm-gutterElement": {
200
- display: "flex",
200
+ position: "relative",
201
+ display: "block",
201
202
  width: "14px",
202
- alignItems: "center",
203
- justifyContent: "flex-end",
204
- padding: "0 1px 0 0"
203
+ padding: "0"
205
204
  },
206
205
  ".cm-foldGutter .cm-gutterElement > span": {
206
+ position: "absolute",
207
+ top: "2px",
208
+ left: "0",
207
209
  display: "inline-flex",
208
- width: "12px",
209
- height: "100%",
210
+ width: "14px",
211
+ height: "14px",
210
212
  alignItems: "center",
211
213
  justifyContent: "center",
212
214
  padding: "0",
213
215
  color: "var(--fg-tertiary)",
214
- lineHeight: "1",
215
- transform: "translateY(-1px)",
216
+ lineHeight: "14px",
216
217
  transition: "color 120ms ease"
217
218
  },
218
219
  ".cm-foldGutter .cm-gutterElement > span:hover": { color: "var(--fg-secondary)" },
@@ -249,16 +250,87 @@ var fractalHighlightStyle = HighlightStyle.define([
249
250
  ]);
250
251
  var jsonSupport = json();
251
252
  var JSON_LANGUAGE_NAMES = /* @__PURE__ */ new Set(["json", "jsonl", "ndjson", "geojson"]);
253
+ var JSON_LINES_LANGUAGE_NAMES = /* @__PURE__ */ new Set(["jsonl", "ndjson"]);
254
+ var JSON_LINES_PRETTY_PRINT_THRESHOLD = 160;
255
+ function formatJsonLinesForDisplay(value) {
256
+ return value.split(/\r?\n/).map((line) => {
257
+ const firstContentIndex = line.search(/\S/);
258
+ if (firstContentIndex < 0 || line.length - firstContentIndex <= JSON_LINES_PRETTY_PRINT_THRESHOLD) return line;
259
+ const indentation = line.slice(0, firstContentIndex);
260
+ try {
261
+ const parsed = JSON.parse(line.slice(firstContentIndex));
262
+ if (parsed === null || typeof parsed !== "object") return line;
263
+ return JSON.stringify(parsed, null, 2).split("\n").map((formattedLine) => `${indentation}${formattedLine}`).join("\n");
264
+ } catch {
265
+ return line;
266
+ }
267
+ }).join("\n");
268
+ }
269
+ function findJsonContainerEnd(document2, start) {
270
+ const openingCharacter = document2[start];
271
+ if (openingCharacter !== "{" && openingCharacter !== "[") return null;
272
+ const closingCharacters = [];
273
+ let isInsideString = false;
274
+ let isEscaped = false;
275
+ for (let index = start; index < document2.length; index += 1) {
276
+ const character = document2[index];
277
+ if (isInsideString) {
278
+ if (isEscaped) {
279
+ isEscaped = false;
280
+ } else if (character === "\\") {
281
+ isEscaped = true;
282
+ } else if (character === '"') {
283
+ isInsideString = false;
284
+ }
285
+ continue;
286
+ }
287
+ if (character === '"') {
288
+ isInsideString = true;
289
+ } else if (character === "{") {
290
+ closingCharacters.push("}");
291
+ } else if (character === "[") {
292
+ closingCharacters.push("]");
293
+ } else if (character === "}" || character === "]") {
294
+ if (closingCharacters.pop() !== character) return null;
295
+ if (closingCharacters.length === 0) return index;
296
+ }
297
+ }
298
+ return null;
299
+ }
300
+ var jsonLinesFoldSupport = foldService.of((state, lineStart, lineEnd) => {
301
+ const line = state.doc.sliceString(lineStart, lineEnd);
302
+ const firstContentIndex = line.search(/\S/);
303
+ if (firstContentIndex < 0) return null;
304
+ const containerStart = lineStart + firstContentIndex;
305
+ const openingCharacter = line[firstContentIndex];
306
+ if (openingCharacter !== "{" && openingCharacter !== "[") return null;
307
+ const document2 = state.doc.toString();
308
+ const containerEnd = findJsonContainerEnd(document2, containerStart);
309
+ if (containerEnd === null || containerEnd - containerStart < 2) return null;
310
+ try {
311
+ const parsed = JSON.parse(document2.slice(containerStart, containerEnd + 1));
312
+ if (parsed === null || typeof parsed !== "object") return null;
313
+ } catch {
314
+ return null;
315
+ }
316
+ return {
317
+ from: containerStart + 1,
318
+ to: containerEnd
319
+ };
320
+ });
252
321
  function resolveLanguage(language, fileName) {
253
322
  const normalized = language?.trim().toLowerCase();
254
323
  const extension = fileName?.split(/[?#]/, 1)[0].split(".").pop()?.toLowerCase();
255
324
  if (normalized && JSON_LANGUAGE_NAMES.has(normalized) || extension && JSON_LANGUAGE_NAMES.has(extension)) {
256
- return { label: normalized === "jsonl" || extension === "jsonl" ? "JSONL" : "JSON", support: jsonSupport };
325
+ const isJsonLines = Boolean(
326
+ normalized && JSON_LINES_LANGUAGE_NAMES.has(normalized) || extension && JSON_LINES_LANGUAGE_NAMES.has(extension)
327
+ );
328
+ return { label: isJsonLines ? "JSONL" : "JSON", support: jsonSupport, isJsonLines };
257
329
  }
258
330
  if (normalized && !["text", "txt", "plaintext", "plain"].includes(normalized)) {
259
- return { label: language?.trim(), support: null };
331
+ return { label: language?.trim(), support: null, isJsonLines: false };
260
332
  }
261
- return { support: null };
333
+ return { support: null, isJsonLines: false };
262
334
  }
263
335
  function CodeBlock({
264
336
  value,
@@ -280,6 +352,10 @@ function CodeBlock({
280
352
  ...props
281
353
  }) {
282
354
  const resolvedLanguage = useMemo(() => resolveLanguage(language, fileName), [fileName, language]);
355
+ const displayValue = useMemo(
356
+ () => !editable && resolvedLanguage.isJsonLines ? formatJsonLinesForDisplay(value) : value,
357
+ [editable, resolvedLanguage.isJsonLines, value]
358
+ );
283
359
  const [copyStatus, setCopyStatus] = useState2("idle");
284
360
  const copyResetTimer = useRef(null);
285
361
  useEffect(() => () => {
@@ -289,9 +365,10 @@ function CodeBlock({
289
365
  codeTheme,
290
366
  syntaxHighlighting(fractalHighlightStyle),
291
367
  ...resolvedLanguage.support ? [resolvedLanguage.support] : [],
368
+ ...resolvedLanguage.isJsonLines ? [jsonLinesFoldSupport] : [],
292
369
  ...lineNumbers ? [foldGutter(), lineNumberGutter()] : [],
293
370
  ...wrap ? [EditorView.lineWrapping] : []
294
- ], [lineNumbers, resolvedLanguage.support, wrap]);
371
+ ], [lineNumbers, resolvedLanguage.isJsonLines, resolvedLanguage.support, wrap]);
295
372
  const languageLabel = resolvedLanguage.label;
296
373
  const toolbarLabel = label ?? fileName ?? languageLabel;
297
374
  const surfaceLabel = ariaLabel ?? (fileName ? `${fileName} ${editable ? "editor" : "viewer"}` : `Code ${editable ? "editor" : "viewer"}`);
@@ -341,7 +418,7 @@ function CodeBlock({
341
418
  CodeMirror,
342
419
  {
343
420
  "aria-label": surfaceLabel,
344
- value,
421
+ value: displayValue,
345
422
  editable,
346
423
  readOnly: !editable,
347
424
  onChange,