@dxos/react-ui-syntax-highlighter 0.8.4-main.c4373fc → 0.8.4-main.c85a9c8dae

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/package.json CHANGED
@@ -1,12 +1,16 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-syntax-highlighter",
3
- "version": "0.8.4-main.c4373fc",
3
+ "version": "0.8.4-main.c85a9c8dae",
4
4
  "description": "A syntax highlighter wrapper.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/dxos/dxos"
10
+ },
7
11
  "license": "MIT",
8
12
  "author": "DXOS.org",
9
- "sideEffects": true,
13
+ "sideEffects": false,
10
14
  "type": "module",
11
15
  "exports": {
12
16
  ".": {
@@ -25,29 +29,28 @@
25
29
  "src"
26
30
  ],
27
31
  "dependencies": {
28
- "@preact-signals/safe-react": "^0.9.0",
29
- "jsonpath": "^1.1.1",
30
- "react-syntax-highlighter": "^15.6.1"
32
+ "jsonpath-plus": "^10.3.0",
33
+ "react-syntax-highlighter": "^15.6.1",
34
+ "@dxos/util": "0.8.4-main.c85a9c8dae"
31
35
  },
32
36
  "devDependencies": {
33
- "@types/jsonpath": "^0.2.4",
34
- "@types/react": "~19.2.2",
35
- "@types/react-dom": "~19.2.1",
37
+ "@types/react": "~19.2.7",
38
+ "@types/react-dom": "~19.2.3",
36
39
  "@types/react-syntax-highlighter": "^15.5.13",
37
- "react": "~19.2.0",
38
- "react-dom": "~19.2.0",
39
- "vite": "7.1.9",
40
- "@dxos/random": "0.8.4-main.c4373fc",
41
- "@dxos/react-ui": "0.8.4-main.c4373fc",
42
- "@dxos/react-ui-theme": "0.8.4-main.c4373fc",
43
- "@dxos/util": "0.8.4-main.c4373fc",
44
- "@dxos/storybook-utils": "0.8.4-main.c4373fc"
40
+ "react": "~19.2.3",
41
+ "react-dom": "~19.2.3",
42
+ "vite": "^7.1.11",
43
+ "@dxos/random": "0.8.4-main.c85a9c8dae",
44
+ "@dxos/util": "0.8.4-main.c85a9c8dae",
45
+ "@dxos/ui-theme": "0.8.4-main.c85a9c8dae",
46
+ "@dxos/storybook-utils": "0.8.4-main.c85a9c8dae",
47
+ "@dxos/react-ui": "0.8.4-main.c85a9c8dae"
45
48
  },
46
49
  "peerDependencies": {
47
- "react": "^19.0.0",
48
- "react-dom": "^19.0.0",
49
- "@dxos/react-ui": "0.8.4-main.c4373fc",
50
- "@dxos/react-ui-theme": "0.8.4-main.c4373fc"
50
+ "react": "~19.2.3",
51
+ "react-dom": "~19.2.3",
52
+ "@dxos/react-ui": "0.8.4-main.c85a9c8dae",
53
+ "@dxos/ui-theme": "0.8.4-main.c85a9c8dae"
51
54
  },
52
55
  "publishConfig": {
53
56
  "access": "public"
@@ -3,9 +3,10 @@
3
3
  //
4
4
 
5
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+ import React from 'react';
6
7
 
7
8
  import { faker } from '@dxos/random';
8
- import { withTheme } from '@dxos/react-ui/testing';
9
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
9
10
 
10
11
  import { Json } from './Json';
11
12
 
@@ -55,10 +56,7 @@ const createData = ({ depth = 2, children = 3 } = {}): any => {
55
56
  const meta = {
56
57
  title: 'ui/react-ui-syntax-highlighter/Json',
57
58
  component: Json,
58
- decorators: [withTheme],
59
- parameters: {
60
- layout: 'column',
61
- },
59
+ decorators: [withTheme(), withLayout({ layout: 'column' })],
62
60
  } satisfies Meta<typeof Json>;
63
61
 
64
62
  export default meta;
@@ -94,3 +92,15 @@ export const Large: Story = {
94
92
  },
95
93
  },
96
94
  };
95
+
96
+ const cycle: any = {
97
+ a: 1,
98
+ b: [],
99
+ };
100
+
101
+ cycle.b.push(cycle);
102
+
103
+ // NOTE: Storybook args cannot be circular.
104
+ export const Cycle: Story = {
105
+ render: () => <Json data={cycle} />,
106
+ };
package/src/Json/Json.tsx CHANGED
@@ -2,16 +2,14 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- // TODO(burdon): Use to jsonpath-plus.
6
- import jp from 'jsonpath';
7
- import React, { useEffect, useState } from 'react';
5
+ import { JSONPath } from 'jsonpath-plus';
6
+ import React, { forwardRef, useEffect, useState } from 'react';
8
7
 
9
8
  import { Input, type ThemedClassName } from '@dxos/react-ui';
9
+ import { type CreateReplacerProps, createReplacer, safeStringify } from '@dxos/util';
10
10
 
11
11
  import { SyntaxHighlighter } from '../SyntaxHighlighter';
12
12
 
13
- const defaultClassNames = '!m-0 grow overflow-y-auto';
14
-
15
13
  export type JsonProps = ThemedClassName<{
16
14
  data?: any;
17
15
  filter?: boolean;
@@ -19,112 +17,63 @@ export type JsonProps = ThemedClassName<{
19
17
  testId?: string;
20
18
  }>;
21
19
 
22
- export const Json = ({ filter, ...params }: JsonProps) => {
23
- if (filter) {
24
- return <JsonFilter {...params} />;
20
+ export const Json = forwardRef<HTMLDivElement, JsonProps>((props, forwardedRef) => {
21
+ if (props.filter) {
22
+ return <JsonFilter {...props} />;
25
23
  }
26
24
 
27
- const { classNames, data, replacer, testId } = params;
25
+ const { classNames, data, replacer, testId } = props;
28
26
  return (
29
- <SyntaxHighlighter language='json' classNames={[defaultClassNames, classNames]} data-testid={testId}>
30
- {JSON.stringify(data, replacer && createReplacer(replacer), 2)}
27
+ <SyntaxHighlighter
28
+ language='json'
29
+ classNames={['w-full overflow-y-auto text-sm', classNames]}
30
+ data-testid={testId}
31
+ ref={forwardedRef}
32
+ >
33
+ {safeStringify(data, replacer && createReplacer(replacer), 2)}
31
34
  </SyntaxHighlighter>
32
35
  );
33
- };
34
-
35
- export const JsonFilter = ({ classNames, data: initialData, replacer, testId }: JsonProps) => {
36
- const [data, setData] = useState(initialData);
37
- const [text, setText] = useState('');
38
- const [error, setError] = useState<Error | null>(null);
39
- useEffect(() => {
40
- if (!initialData || !text.trim().length) {
41
- setData(initialData);
42
- } else {
43
- try {
44
- setData(jp.query(initialData, text));
45
- setError(null);
46
- } catch (err) {
47
- setData(initialData);
48
- setError(err as Error);
49
- }
50
- }
51
- }, [initialData, text]); // TODO(burdon): Need structural diff.
52
-
53
- return (
54
- <div className='flex flex-col grow overflow-hidden'>
55
- <Input.Root validationValence={error ? 'error' : 'success'}>
56
- <Input.TextInput
57
- classNames={['p-1 px-2 font-mono', error && 'border-red-500']}
58
- variant='subdued'
59
- value={text}
60
- onChange={(event) => setText(event.target.value)}
61
- placeholder='JSONPath (e.g., $.graph.nodes)'
62
- />
63
- </Input.Root>
64
- <SyntaxHighlighter language='json' classNames={[defaultClassNames, classNames]} data-testid={testId}>
65
- {JSON.stringify(data, replacer && createReplacer(replacer), 2)}
66
- </SyntaxHighlighter>
67
- </div>
68
- );
69
- };
70
-
71
- export type CreateReplacerProps = {
72
- omit?: string[];
73
- parse?: string[];
74
- maxDepth?: number;
75
- maxArrayLen?: number;
76
- maxStringLen?: number;
77
- };
36
+ });
78
37
 
79
- export type JsonReplacer = (this: any, key: string, value: any) => any;
38
+ export const JsonFilter = forwardRef<HTMLDivElement, JsonProps>(
39
+ ({ classNames, data: initialData, replacer, testId }, forwardedRef) => {
40
+ const [data, setData] = useState(initialData);
41
+ const [text, setText] = useState('');
42
+ const [error, setError] = useState<Error | null>(null);
80
43
 
81
- export const createReplacer = ({
82
- omit,
83
- parse,
84
- maxDepth,
85
- maxArrayLen,
86
- maxStringLen,
87
- }: CreateReplacerProps): JsonReplacer => {
88
- let currentDepth = 0;
89
- const depthMap = new WeakMap<object, number>();
90
-
91
- return function (this: any, key: string, value: any) {
92
- // Track depth.
93
- if (key === '') {
94
- currentDepth = 0;
95
- } else if (this && typeof this === 'object') {
96
- const parentDepth = depthMap.get(this) ?? 0;
97
- currentDepth = parentDepth + 1;
98
- }
99
-
100
- // Store depth for this object.
101
- if (value && typeof value === 'object') {
102
- depthMap.set(value, currentDepth);
103
-
104
- // Check max depth.
105
- if (maxDepth != null && currentDepth >= maxDepth) {
106
- return Array.isArray(value) ? `[{ length: ${value.length} }]` : `{ keys: ${Object.keys(value).length} }`;
107
- }
108
- }
109
-
110
- // Apply other filters.
111
- if (omit?.includes(key)) {
112
- return undefined;
113
- }
114
- if (parse?.includes(key) && typeof value === 'string') {
115
- try {
116
- return JSON.parse(value);
117
- } catch {
118
- return value;
44
+ useEffect(() => {
45
+ if (!initialData || !text.trim().length) {
46
+ setData(initialData);
47
+ } else {
48
+ try {
49
+ setData(JSONPath({ path: text, json: initialData }));
50
+ setError(null);
51
+ } catch (err) {
52
+ setData(initialData);
53
+ setError(err as Error);
54
+ }
119
55
  }
120
- }
121
- if (maxArrayLen != null && Array.isArray(value) && value.length > maxArrayLen) {
122
- return `[length: ${value.length}]`;
123
- }
124
- if (maxStringLen != null && typeof value === 'string' && value.length > maxStringLen) {
125
- return value.slice(0, maxStringLen) + '...';
126
- }
127
-
128
- return value;
129
- };
130
- };
56
+ }, [initialData, text]); // TODO(burdon): Need structural diff.
57
+
58
+ return (
59
+ <div className='flex flex-col h-full overflow-hidden' ref={forwardedRef}>
60
+ <Input.Root validationValence={error ? 'error' : 'success'}>
61
+ <Input.TextInput
62
+ classNames={['p-1 px-2 font-mono', error && 'border-rose-500']}
63
+ variant='subdued'
64
+ value={text}
65
+ placeholder='JSONPath (e.g., $.graph.nodes)'
66
+ onChange={(event) => setText(event.target.value)}
67
+ />
68
+ </Input.Root>
69
+ <SyntaxHighlighter
70
+ language='json'
71
+ classNames={['w-full overflow-y-auto text-sm', classNames]}
72
+ data-testid={testId}
73
+ >
74
+ {safeStringify(data, replacer && createReplacer(replacer), 2)}
75
+ </SyntaxHighlighter>
76
+ </div>
77
+ );
78
+ },
79
+ );
@@ -4,15 +4,21 @@
4
4
 
5
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
6
6
 
7
- import { withTheme } from '@dxos/react-ui/testing';
7
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
8
8
  import { trim } from '@dxos/util';
9
9
 
10
+ // @ts-ignore - Vite raw import.
11
+ import TEXT from '../../package.json?raw';
12
+
10
13
  import { SyntaxHighlighter } from './SyntaxHighlighter';
11
14
 
12
15
  const meta = {
13
16
  title: 'ui/react-ui-syntax-highlighter/SyntaxHighlighter',
14
17
  component: SyntaxHighlighter,
15
- decorators: [withTheme],
18
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
19
+ parameters: {
20
+ layout: 'fullscreen',
21
+ },
16
22
  } satisfies Meta<typeof SyntaxHighlighter>;
17
23
 
18
24
  export default meta;
@@ -23,7 +29,7 @@ export const Default: Story = {
23
29
  args: {
24
30
  language: 'json',
25
31
  classNames: 'text-sm',
26
- children: JSON.stringify({ message: 'DXOS', initialized: true }, null, 2),
32
+ children: TEXT,
27
33
  },
28
34
  };
29
35
 
@@ -32,7 +38,7 @@ export const Typescript: Story = {
32
38
  language: 'tsx',
33
39
  children: trim`
34
40
  import React from 'react'
35
-
41
+
36
42
  const Test = () => {
37
43
  return <div>Test</div>
38
44
  }
@@ -40,8 +46,4 @@ export const Typescript: Story = {
40
46
  },
41
47
  };
42
48
 
43
- export const Empty: Story = {
44
- args: {
45
- children: false,
46
- },
47
- };
49
+ export const Empty: Story = {};
@@ -7,8 +7,8 @@ import { type SyntaxHighlighterProps as NaturalSyntaxHighlighterProps } from 're
7
7
  import NativeSyntaxHighlighter from 'react-syntax-highlighter/dist/esm/prism-async-light';
8
8
  import { coldarkDark as dark, coldarkCold as light } from 'react-syntax-highlighter/dist/esm/styles/prism';
9
9
 
10
- import { type ThemedClassName, useThemeContext } from '@dxos/react-ui';
11
- import { mx } from '@dxos/react-ui-theme';
10
+ import { ScrollArea, type ThemedClassName, useThemeContext } from '@dxos/react-ui';
11
+ import { mx } from '@dxos/ui-theme';
12
12
 
13
13
  const zeroWidthSpace = '\u200b';
14
14
 
@@ -36,24 +36,27 @@ export const SyntaxHighlighter = ({
36
36
  const { themeMode } = useThemeContext();
37
37
 
38
38
  return (
39
- <div className={mx('flex is-full p-1 overflow-hidden text-baseText', classNames)}>
40
- <NativeSyntaxHighlighter
41
- className='is-full overflow-auto scrollbar-thin'
42
- language={languages[language as keyof typeof languages] || language}
43
- style={themeMode === 'dark' ? dark : light}
44
- customStyle={{
45
- background: 'unset',
46
- border: 'none',
47
- boxShadow: 'none',
48
- padding: 0,
49
- margin: 0,
50
- }}
51
- {...props}
52
- >
53
- {/* Non-empty fallback prevents collapse. */}
54
- {children || fallback}
55
- </NativeSyntaxHighlighter>
56
- </div>
39
+ <ScrollArea.Root thin classNames={mx('p1', classNames)}>
40
+ <ScrollArea.Viewport>
41
+ <div role='none'>
42
+ <NativeSyntaxHighlighter
43
+ language={languages[language as keyof typeof languages] || language}
44
+ style={themeMode === 'dark' ? dark : light}
45
+ customStyle={{
46
+ background: 'unset',
47
+ border: 'none',
48
+ boxShadow: 'none',
49
+ padding: 0,
50
+ margin: 0,
51
+ }}
52
+ {...props}
53
+ >
54
+ {/* Non-empty fallback prevents collapse. */}
55
+ {children || fallback}
56
+ </NativeSyntaxHighlighter>
57
+ </div>
58
+ </ScrollArea.Viewport>
59
+ </ScrollArea.Root>
57
60
  );
58
61
  };
59
62