@uiw/react-json-view 1.1.1 → 1.3.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
@@ -4,6 +4,7 @@ react-json-view
4
4
  [![CI](https://github.com/uiwjs/react-json-view/actions/workflows/ci.yml/badge.svg)](https://github.com/uiwjs/react-json-view/actions/workflows/ci.yml)
5
5
  [![npm version](https://img.shields.io/npm/v/@uiw/react-json-view.svg)](https://www.npmjs.com/package/@uiw/react-json-view)
6
6
  [![react@^18](https://shields.io/badge/react-^18-green?style=flat&logo=react)](https://github.com/facebook/react/releases)
7
+ [![Coverage Status](https://uiwjs.github.io/react-json-view/badges.svg)](https://uiwjs.github.io/react-json-view/lcov-report/)
7
8
 
8
9
  A React component for displaying and editing javascript arrays and JSON objects.
9
10
 
@@ -22,7 +23,8 @@ A React component for displaying and editing javascript arrays and JSON objects.
22
23
  📚 Use Typescript to write, better code hints.
23
24
  🎨 Support theme customization & [`online editing`](https://uiwjs.github.io/react-json-view/#online-editing-theme) theme
24
25
  🌒 Support dark/light mode
25
- 📦 Zero dependencies
26
+ 📦 Zero dependencies
27
+ ♻️ Whether to highlight updates.
26
28
 
27
29
  ## Quick Start
28
30
 
@@ -111,6 +113,7 @@ const customTheme = {
111
113
  '--w-rjv-line-color': '#323232',
112
114
  '--w-rjv-arrow-color': 'var(--w-rjv-color)',
113
115
  '--w-rjv-info-color': '#656565',
116
+ '--w-rjv-update-color': '#ebcb8b',
114
117
  '--w-rjv-copied-color': '#9cdcfe',
115
118
  '--w-rjv-copied-success-color': '#28a745',
116
119
 
@@ -140,7 +143,7 @@ export default function Demo() {
140
143
  Online custom style example, please check in the [documentation website](https://uiwjs.github.io/react-json-view/)
141
144
 
142
145
  ```tsx mdx:preview:&title=Online Editing Theme
143
- import React, { useState, useRef } from 'react';
146
+ import React, { useState, useEffect, useRef } from 'react';
144
147
  import Colorful from '@uiw/react-color-colorful';
145
148
  import JsonView from '@uiw/react-json-view';
146
149
 
@@ -172,6 +175,7 @@ const customTheme = {
172
175
  '--w-rjv-line-color': '#323232',
173
176
  '--w-rjv-arrow-color': '#9cdcfe',
174
177
  '--w-rjv-info-color': '#656565',
178
+ '--w-rjv-update-color': '#ebcb8b',
175
179
  '--w-rjv-copied-color': '#0184a6',
176
180
  '--w-rjv-copied-success-color': '#28a745',
177
181
 
@@ -197,10 +201,23 @@ export default function Demo() {
197
201
  setHex(hexa);
198
202
  setTheme({ ...theme, [cssvar]: hexa });
199
203
  };
204
+
205
+ const [src, setSrc] = useState({ ...object })
206
+ useEffect(() => {
207
+ const loop = () => {
208
+ setSrc(src => ({
209
+ ...src,
210
+ timer: src.timer + 1
211
+ }))
212
+ }
213
+ const id = setInterval(loop, 1000)
214
+ return () => clearInterval(id)
215
+ }, []);
216
+
200
217
  return (
201
218
  <React.Fragment>
202
219
  <div style={{ display: 'flex', gap: '1rem' }}>
203
- <JsonView value={object} keyName="root" style={{ flex: 1, ...theme }} />
220
+ <JsonView value={src} keyName="root" style={{ flex: 1, ...theme }} />
204
221
  <div>
205
222
  <Colorful color={hex} onChange={onChange} />
206
223
  <div style={{ display: 'flex', gap: '0.4rem', flexWrap: 'wrap' }}>
@@ -305,6 +322,50 @@ export default function Demo() {
305
322
  }
306
323
  ```
307
324
 
325
+ ## Highlight Updates
326
+
327
+ ```tsx mdx:preview
328
+ import React, { useState, useEffect } from 'react';
329
+ import JsonView from '@uiw/react-json-view';
330
+ import { TriangleArrow } from '@uiw/react-json-view/triangle-arrow';
331
+ import { TriangleSolidArrow } from '@uiw/react-json-view/triangle-solid-arrow';
332
+
333
+ const object = {
334
+ string: 'Lorem ipsum dolor sit amet',
335
+ integer: 42,
336
+ timer: 0,
337
+ object: { 'first-child': true, 'second-child': false, 'last-child': null },
338
+ }
339
+ export default function Demo() {
340
+ const [src, setSrc] = useState({ ...object })
341
+ useEffect(() => {
342
+ const loop = () => {
343
+ setSrc(src => ({
344
+ ...src,
345
+ timer: src.timer + 1
346
+ }))
347
+ }
348
+ const id = setInterval(loop, 1000)
349
+ return () => clearInterval(id)
350
+ }, []);
351
+
352
+ return (
353
+ <JsonView
354
+ value={src}
355
+ keyName="root"
356
+ style={{
357
+ '--w-rjv-background-color': '#ffffff',
358
+ '--w-rjv-border-left': '1px dashed #ebebeb',
359
+ // ✅ Change default update background color ✅
360
+ '--w-rjv-update-color': '#ff6ffd',
361
+ }}
362
+ />
363
+ )
364
+ }
365
+ ```
366
+
367
+ This feature can be disabled with `highlightUpdates={false}`, and the default color can be changed with `--w-rjv-update-color`.
368
+
308
369
  ## Modify Icon Style
309
370
 
310
371
  Use built-in default icons.
@@ -368,34 +429,25 @@ const object = {
368
429
  }
369
430
 
370
431
  const Arrow = (props) => {
371
- const { style, 'data-expand': expand, ...reset } = props;
372
- const defaultStyle = {
373
- cursor: 'pointer',
374
- height: '1em',
375
- width: '1em',
376
- };
432
+ const { style, 'data-expand': expand, ...reset } = props;
433
+ const svgProps = {
434
+ style: { cursor: 'pointer', height: '1em', width: '1em' },
435
+ viewBox: '0 0 24 24',
436
+ fill: 'var(--w-rjv-arrow-color, currentColor)',
437
+ ...reset
438
+ }
377
439
  if (!expand) {
378
440
  return (
379
- <svg
380
- viewBox="0 0 24 24"
381
- fill="var(--w-rjv-arrow-color, currentColor)"
382
- style={defaultStyle}
383
- {...reset}
384
- >
441
+ <svg {...svgProps}>
385
442
  <path d="M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M13,7H11V11H7V13H11V17H13V13H17V11H13V7Z" />
386
443
  </svg>
387
- )
444
+ );
388
445
  }
389
446
  return (
390
- <svg
391
- viewBox="0 0 24 24"
392
- fill="var(--w-rjv-arrow-color, currentColor)"
393
- style={defaultStyle}
394
- {...reset}
395
- >
447
+ <svg {...svgProps}>
396
448
  <path d="M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M7,13H17V11H7" />
397
449
  </svg>
398
- )
450
+ );
399
451
  }
400
452
 
401
453
  export default function Demo() {
@@ -433,6 +485,8 @@ export interface JsonViewProps<T> extends React.DetailedHTMLProps<React.HTMLAttr
433
485
  keyName?: string | number;
434
486
  /** The user can copy objects and arrays to clipboard by clicking on the clipboard icon. @default true */
435
487
  enableClipboard?: boolean;
488
+ /** Whether to highlight updates. @default true */
489
+ highlightUpdates?: boolean;
436
490
  /** Display for quotes in object-key @default " */
437
491
  quotes?: "'" | '"' | '';
438
492
  /** When set to true, all nodes will be collapsed by default. Use an integer value to collapse at a particular depth. @default false */
package/cjs/copied.d.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  export interface CopiedProps<T = object> extends React.SVGProps<SVGSVGElement> {
3
3
  show?: boolean;
4
4
  text?: T;
5
+ onCopied?: (text: string, obj: T | '') => void;
5
6
  render?: (props: Omit<CopiedProps<T>, 'render'>) => JSX.Element;
6
7
  }
7
8
  export declare function Copied<T>(props: CopiedProps<T>): JSX.Element | null;
package/cjs/copied.js CHANGED
@@ -10,12 +10,13 @@ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/sli
10
10
  var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
11
11
  var _react = require("react");
12
12
  var _jsxRuntime = require("react/jsx-runtime");
13
- var _excluded = ["children", "style", "text", "render", "show"];
13
+ var _excluded = ["children", "style", "text", "onCopied", "render", "show"];
14
14
  function Copied(props) {
15
15
  var children = props.children,
16
16
  style = props.style,
17
17
  _props$text = props.text,
18
18
  text = _props$text === void 0 ? '' : _props$text,
19
+ onCopied = props.onCopied,
19
20
  render = props.render,
20
21
  show = props.show,
21
22
  reset = (0, _objectWithoutProperties2["default"])(props, _excluded);
@@ -26,7 +27,7 @@ function Copied(props) {
26
27
  setCopied = _useState2[1];
27
28
  var click = function click(event) {
28
29
  event.stopPropagation();
29
- var copyText = JSON.stringify(text, function (key, value) {
30
+ var copyText = JSON.stringify(text || '', function (key, value) {
30
31
  if (typeof value === 'bigint') {
31
32
  return value.toString();
32
33
  }
@@ -34,6 +35,7 @@ function Copied(props) {
34
35
  }, 2);
35
36
  if (text === Infinity) copyText = Infinity;
36
37
  navigator.clipboard.writeText(copyText).then(function () {
38
+ onCopied && onCopied(copyText, text);
37
39
  setCopied(true);
38
40
  var timer = setTimeout(function () {
39
41
  setCopied(false);
package/cjs/index.d.ts CHANGED
@@ -17,6 +17,8 @@ export interface JsonViewProps<T extends object> extends React.DetailedHTMLProps
17
17
  keyName?: string | number;
18
18
  /** The user can copy objects and arrays to clipboard by clicking on the clipboard icon. @default true */
19
19
  enableClipboard?: boolean;
20
+ /** Whether to highlight updates. @default true */
21
+ highlightUpdates?: boolean;
20
22
  /** Display for quotes in object-key @default " */
21
23
  quotes?: "'" | '"' | '';
22
24
  /** When set to true, all nodes will be collapsed by default. Use an integer value to collapse at a particular depth. @default false */
@@ -28,6 +30,8 @@ export interface JsonViewProps<T extends object> extends React.DetailedHTMLProps
28
30
  keyid: string;
29
31
  keyName?: string | number;
30
32
  }) => void;
33
+ /** Fires event when you copy */
34
+ onCopied?: CopiedProps<T>['onCopied'];
31
35
  /** Redefine interface elements to re-render. */
32
36
  components?: {
33
37
  braces?: MetaProps['render'];
package/cjs/node.d.ts CHANGED
@@ -6,6 +6,7 @@ export interface MetaProps extends LabelProps {
6
6
  start?: boolean;
7
7
  render?: (props: Pick<MetaProps, 'start' | 'isArray' | 'className' | 'children'>) => JSX.Element;
8
8
  }
9
+ export declare function usePrevious<T>(value: T): T | undefined;
9
10
  export declare function Meta(props: MetaProps): JSX.Element;
10
11
  export interface EllipsisProps extends React.HTMLAttributes<HTMLSpanElement> {
11
12
  render?: (props: EllipsisProps) => JSX.Element;
@@ -13,6 +14,7 @@ export interface EllipsisProps extends React.HTMLAttributes<HTMLSpanElement> {
13
14
  export declare const Ellipsis: FC<PropsWithChildren<EllipsisProps>>;
14
15
  export interface SemicolonProps extends LabelProps {
15
16
  show?: boolean;
17
+ highlightUpdates?: boolean;
16
18
  quotes?: JsonViewProps<object>['quotes'];
17
19
  value?: object;
18
20
  render?: (props: Omit<SemicolonProps, 'show'> & {}) => JSX.Element;
package/cjs/node.js CHANGED
@@ -8,8 +8,9 @@ exports.Ellipsis = exports.CountInfo = void 0;
8
8
  exports.Meta = Meta;
9
9
  exports.RooNode = RooNode;
10
10
  exports.Semicolon = void 0;
11
- var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
11
+ exports.usePrevious = usePrevious;
12
12
  var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
13
+ var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
13
14
  var _objectSpread2 = _interopRequireDefault(require("@babel/runtime/helpers/objectSpread2"));
14
15
  var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
15
16
  var _react = require("react");
@@ -20,8 +21,15 @@ var _copied = require("./copied");
20
21
  var _jsxRuntime = require("react/jsx-runtime");
21
22
  var _excluded = ["isArray", "start", "className", "render"],
22
23
  _excluded2 = ["style", "render"],
23
- _excluded3 = ["children", "render", "color", "value", "className", "show", "quotes"],
24
- _excluded4 = ["value", "keyName", "displayDataTypes", "components", "displayObjectSize", "enableClipboard", "indentWidth", "collapsed", "level", "keyid", "quotes", "onExpand"];
24
+ _excluded3 = ["children", "render", "color", "value", "className", "show", "highlightUpdates", "quotes"],
25
+ _excluded4 = ["value", "keyName", "className", "displayDataTypes", "components", "displayObjectSize", "enableClipboard", "highlightUpdates", "indentWidth", "collapsed", "level", "keyid", "quotes", "onCopied", "onExpand"];
26
+ function usePrevious(value) {
27
+ var ref = (0, _react.useRef)();
28
+ (0, _react.useEffect)(function () {
29
+ ref.current = value;
30
+ });
31
+ return ref.current;
32
+ }
25
33
  function Meta(props) {
26
34
  var _props$isArray = props.isArray,
27
35
  isArray = _props$isArray === void 0 ? false : _props$isArray,
@@ -77,8 +85,50 @@ var Semicolon = function Semicolon(_ref2) {
77
85
  _ref2$className = _ref2.className,
78
86
  className = _ref2$className === void 0 ? 'w-rjv-object-key' : _ref2$className,
79
87
  show = _ref2.show,
88
+ highlightUpdates = _ref2.highlightUpdates,
80
89
  quotes = _ref2.quotes,
81
90
  props = (0, _objectWithoutProperties2["default"])(_ref2, _excluded3);
91
+ var prevValue = usePrevious(value);
92
+ var highlightContainer = (0, _react.useRef)(null);
93
+ var isHighlight = (0, _react.useMemo)(function () {
94
+ if (!highlightUpdates || prevValue === undefined) return false;
95
+ // highlight if value type changed
96
+ if ((0, _typeof2["default"])(value) !== (0, _typeof2["default"])(prevValue)) {
97
+ return true;
98
+ }
99
+ if (typeof value === 'number') {
100
+ // notice: NaN !== NaN
101
+ if (isNaN(value) && isNaN(prevValue)) return false;
102
+ return value !== prevValue;
103
+ }
104
+ // highlight if isArray changed
105
+ if (Array.isArray(value) !== Array.isArray(prevValue)) {
106
+ return true;
107
+ }
108
+ // not highlight object/function
109
+ // deep compare they will be slow
110
+ if ((0, _typeof2["default"])(value) === 'object' || typeof value === 'function') {
111
+ return false;
112
+ }
113
+
114
+ // highlight if not equal
115
+ if (value !== prevValue) {
116
+ return true;
117
+ }
118
+ return false;
119
+ }, [highlightUpdates, value]);
120
+ (0, _react.useEffect)(function () {
121
+ if (highlightContainer.current && isHighlight && 'animate' in highlightContainer.current) {
122
+ highlightContainer.current.animate([{
123
+ backgroundColor: 'var(--w-rjv-update-color, #ebcb8b)'
124
+ }, {
125
+ backgroundColor: ''
126
+ }], {
127
+ duration: 1000,
128
+ easing: 'ease-in'
129
+ });
130
+ }
131
+ }, [isHighlight, value]);
82
132
  var content = show ? "".concat(quotes).concat(children).concat(quotes) : children;
83
133
  if (render) return render((0, _objectSpread2["default"])((0, _objectSpread2["default"])({
84
134
  className: className
@@ -93,6 +143,7 @@ var Semicolon = function Semicolon(_ref2) {
93
143
  className: className,
94
144
  color: color
95
145
  }, props), {}, {
146
+ ref: highlightContainer,
96
147
  children: content
97
148
  }));
98
149
  };
@@ -105,7 +156,7 @@ var CountInfo = function CountInfo(_ref3) {
105
156
  fontStyle: 'italic'
106
157
  },
107
158
  color: "var(--w-rjv-info-color, #0000004d)",
108
- className: "object-size",
159
+ className: "w-rjv-object-size",
109
160
  children: children
110
161
  });
111
162
  };
@@ -115,6 +166,7 @@ function RooNode(props) {
115
166
  var _props$value = props.value,
116
167
  value = _props$value === void 0 ? {} : _props$value,
117
168
  keyName = props.keyName,
169
+ className = props.className,
118
170
  _props$displayDataTyp = props.displayDataTypes,
119
171
  displayDataTypes = _props$displayDataTyp === void 0 ? true : _props$displayDataTyp,
120
172
  _props$components = props.components,
@@ -123,6 +175,8 @@ function RooNode(props) {
123
175
  displayObjectSize = _props$displayObjectS === void 0 ? true : _props$displayObjectS,
124
176
  _props$enableClipboar = props.enableClipboard,
125
177
  enableClipboard = _props$enableClipboar === void 0 ? true : _props$enableClipboar,
178
+ _props$highlightUpdat = props.highlightUpdates,
179
+ highlightUpdates = _props$highlightUpdat === void 0 ? true : _props$highlightUpdat,
126
180
  _props$indentWidth = props.indentWidth,
127
181
  indentWidth = _props$indentWidth === void 0 ? 15 : _props$indentWidth,
128
182
  collapsed = props.collapsed,
@@ -132,6 +186,7 @@ function RooNode(props) {
132
186
  keyid = _props$keyid === void 0 ? 'root' : _props$keyid,
133
187
  _props$quotes = props.quotes,
134
188
  quotes = _props$quotes === void 0 ? '"' : _props$quotes,
189
+ onCopied = props.onCopied,
135
190
  onExpand = props.onExpand,
136
191
  reset = (0, _objectWithoutProperties2["default"])(props, _excluded4);
137
192
  var isArray = Array.isArray(value);
@@ -160,6 +215,8 @@ function RooNode(props) {
160
215
  displayDataTypes: displayDataTypes,
161
216
  displayObjectSize: displayObjectSize,
162
217
  enableClipboard: enableClipboard,
218
+ highlightUpdates: highlightUpdates,
219
+ onCopied: onCopied,
163
220
  onExpand: onExpand,
164
221
  collapsed: collapsed,
165
222
  quotes: quotes,
@@ -173,13 +230,16 @@ function RooNode(props) {
173
230
  displayObjectSize: displayObjectSize,
174
231
  enableClipboard: enableClipboard,
175
232
  indentWidth: indentWidth,
233
+ renderBraces: components.braces,
176
234
  renderValue: components.value
177
235
  };
178
236
  var arrowView = components.arrow ? /*#__PURE__*/(0, _react.cloneElement)(components.arrow, {
179
237
  style: arrowStyle,
180
- 'data-expand': expand
238
+ 'data-expand': expand,
239
+ className: "w-rjv-arrow"
181
240
  }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_TriangleArrow.TriangleArrow, {
182
- style: arrowStyle
241
+ style: arrowStyle,
242
+ className: "w-rjv-arrow"
183
243
  });
184
244
  var _useState = (0, _react.useState)(false),
185
245
  _useState2 = (0, _slicedToArray2["default"])(_useState, 2),
@@ -188,6 +248,7 @@ function RooNode(props) {
188
248
  var tools = enableClipboard ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_copied.Copied, {
189
249
  show: showTools,
190
250
  text: value,
251
+ onCopied: onCopied,
191
252
  render: components.copied
192
253
  }) : undefined;
193
254
  var eventProps = {};
@@ -199,7 +260,9 @@ function RooNode(props) {
199
260
  return setShowTools(false);
200
261
  };
201
262
  }
202
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", (0, _objectSpread2["default"])((0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, reset), eventProps), {}, {
263
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", (0, _objectSpread2["default"])((0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, reset), {}, {
264
+ className: "".concat(className, " w-rjv-inner")
265
+ }, eventProps), {}, {
203
266
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_value.Line, {
204
267
  style: {
205
268
  display: 'inline-flex',
@@ -228,6 +291,7 @@ function RooNode(props) {
228
291
  children: [nameKeys.length, " items"]
229
292
  }), tools]
230
293
  }), expand && /*#__PURE__*/(0, _jsxRuntime.jsx)(_value.Line, {
294
+ className: "w-rjv-content",
231
295
  style: {
232
296
  borderLeft: 'var(--w-rjv-border-left-width, 1px) solid var(--w-rjv-line-color, #ebebeb)',
233
297
  marginLeft: 6
@@ -237,6 +301,7 @@ function RooNode(props) {
237
301
  var renderKey = /*#__PURE__*/(0, _jsxRuntime.jsx)(Semicolon, {
238
302
  value: item,
239
303
  quotes: quotes,
304
+ highlightUpdates: highlightUpdates,
240
305
  render: components.objectKey,
241
306
  color: typeof key === 'number' ? _value.typeMap['number'].color : '',
242
307
  show: typeof key === 'string',
@@ -246,6 +311,7 @@ function RooNode(props) {
246
311
  if (Array.isArray(item) && !isEmpty) {
247
312
  var label = isArray ? idx : key;
248
313
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_value.Line, {
314
+ className: "w-rjv-wrap",
249
315
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(RooNode, (0, _objectSpread2["default"])({
250
316
  value: item,
251
317
  keyid: keyid + subkeyid + label,
@@ -255,6 +321,7 @@ function RooNode(props) {
255
321
  }
256
322
  if ((0, _typeof2["default"])(item) === 'object' && item && !(item instanceof Date) && !isEmpty) {
257
323
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_value.Line, {
324
+ className: "w-rjv-wrap",
258
325
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(RooNode, (0, _objectSpread2["default"])({
259
326
  keyid: keyid + subkeyid + key,
260
327
  value: item,
@@ -266,7 +333,6 @@ function RooNode(props) {
266
333
  return;
267
334
  }
268
335
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_value.ValueView, (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, valueViewProps), {}, {
269
- renderBraces: components.braces,
270
336
  renderKey: renderKey,
271
337
  keyName: key,
272
338
  value: item
@@ -5,6 +5,7 @@ export declare const darkTheme: {
5
5
  '--w-rjv-line-color': string;
6
6
  '--w-rjv-arrow-color': string;
7
7
  '--w-rjv-info-color': string;
8
+ '--w-rjv-update-color': string;
8
9
  '--w-rjv-copied-color': string;
9
10
  '--w-rjv-copied-success-color': string;
10
11
  '--w-rjv-curlybraces-color': string;
package/cjs/theme/dark.js CHANGED
@@ -11,6 +11,7 @@ var darkTheme = {
11
11
  '--w-rjv-line-color': '#323232',
12
12
  '--w-rjv-arrow-color': 'var(--w-rjv-color)',
13
13
  '--w-rjv-info-color': '#656565',
14
+ '--w-rjv-update-color': '#ebcb8b',
14
15
  '--w-rjv-copied-color': '#0184a6',
15
16
  '--w-rjv-copied-success-color': '#28a745',
16
17
  '--w-rjv-curlybraces-color': '#1896b6',
@@ -5,6 +5,7 @@ export declare const lightTheme: {
5
5
  '--w-rjv-line-color': string;
6
6
  '--w-rjv-arrow-color': string;
7
7
  '--w-rjv-info-color': string;
8
+ '--w-rjv-update-color': string;
8
9
  '--w-rjv-copied-color': string;
9
10
  '--w-rjv-copied-success-color': string;
10
11
  '--w-rjv-curlybraces-color': string;
@@ -11,6 +11,7 @@ var lightTheme = {
11
11
  '--w-rjv-line-color': '#ebebeb',
12
12
  '--w-rjv-arrow-color': 'var(--w-rjv-color)',
13
13
  '--w-rjv-info-color': '#0000004d',
14
+ '--w-rjv-update-color': '#ebcb8b',
14
15
  '--w-rjv-copied-color': '#002b36',
15
16
  '--w-rjv-copied-success-color': '#28a745',
16
17
  '--w-rjv-curlybraces-color': '#236a7c',
package/cjs/value.d.ts CHANGED
@@ -60,7 +60,7 @@ export interface LabelProps extends React.HTMLAttributes<HTMLSpanElement> {
60
60
  opacity?: number;
61
61
  paddingRight?: number;
62
62
  }
63
- export declare const Label: FC<PropsWithChildren<LabelProps>>;
63
+ export declare const Label: import("react").ForwardRefExoticComponent<LabelProps & import("react").RefAttributes<HTMLSpanElement>>;
64
64
  export interface TypeProps extends React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement> {
65
65
  type: keyof typeof typeMap;
66
66
  }
package/cjs/value.js CHANGED
@@ -142,7 +142,12 @@ function ValueView(props) {
142
142
  show: showTools,
143
143
  text: value
144
144
  }) : undefined;
145
- var eventProps = {};
145
+ var eventProps = {
146
+ className: 'w-rjv-line',
147
+ style: {
148
+ paddingLeft: indentWidth
149
+ }
150
+ };
146
151
  if (enableClipboard) {
147
152
  eventProps.onMouseEnter = function () {
148
153
  return setShowTools(true);
@@ -167,12 +172,9 @@ function ValueView(props) {
167
172
  className: "w-rjv-value",
168
173
  children: content
169
174
  });
170
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(Line, (0, _objectSpread2["default"])((0, _objectSpread2["default"])({
171
- style: {
172
- paddingLeft: indentWidth
173
- }
174
- }, eventProps), {}, {
175
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(Line, (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, eventProps), {}, {
175
176
  children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(Label, (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, reset), {}, {
177
+ ref: null,
176
178
  children: [renderKey, /*#__PURE__*/(0, _jsxRuntime.jsx)(Colon, {}), typeView, valueView, tools]
177
179
  }))
178
180
  }));
@@ -190,17 +192,14 @@ function ValueView(props) {
190
192
  children: [length, " items"]
191
193
  })]
192
194
  });
193
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(Line, (0, _objectSpread2["default"])((0, _objectSpread2["default"])({
194
- style: {
195
- paddingLeft: indentWidth
196
- }
197
- }, eventProps), {}, {
195
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(Line, (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, eventProps), {}, {
198
196
  children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(Label, (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, reset), {}, {
197
+ ref: null,
199
198
  children: [renderKey, /*#__PURE__*/(0, _jsxRuntime.jsx)(Colon, {}), typeView, empty, tools]
200
199
  }))
201
200
  }));
202
201
  }
203
- var Label = function Label(_ref2) {
202
+ var Label = /*#__PURE__*/(0, _react.forwardRef)(function (_ref2, ref) {
204
203
  var children = _ref2.children,
205
204
  color = _ref2.color,
206
205
  fontSize = _ref2.fontSize,
@@ -216,9 +215,10 @@ var Label = function Label(_ref2) {
216
215
  paddingRight: paddingRight
217
216
  }, style)
218
217
  }, reset), {}, {
218
+ ref: ref,
219
219
  children: children
220
220
  }));
221
- };
221
+ });
222
222
  exports.Label = Label;
223
223
  var Type = function Type(props) {
224
224
  var _typeMap$type2, _typeMap$type3;
package/esm/copied.d.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  export interface CopiedProps<T = object> extends React.SVGProps<SVGSVGElement> {
3
3
  show?: boolean;
4
4
  text?: T;
5
+ onCopied?: (text: string, obj: T | '') => void;
5
6
  render?: (props: Omit<CopiedProps<T>, 'render'>) => JSX.Element;
6
7
  }
7
8
  export declare function Copied<T>(props: CopiedProps<T>): JSX.Element | null;
package/esm/copied.js CHANGED
@@ -1,12 +1,13 @@
1
1
  import _extends from "@babel/runtime/helpers/extends";
2
2
  import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/objectWithoutPropertiesLoose";
3
- var _excluded = ["children", "style", "text", "render", "show"];
3
+ var _excluded = ["children", "style", "text", "onCopied", "render", "show"];
4
4
  import { useState } from 'react';
5
5
  import { jsx as _jsx } from "react/jsx-runtime";
6
6
  export function Copied(props) {
7
7
  var {
8
8
  style,
9
9
  text = '',
10
+ onCopied,
10
11
  render,
11
12
  show
12
13
  } = props,
@@ -15,7 +16,7 @@ export function Copied(props) {
15
16
  var [copied, setCopied] = useState(false);
16
17
  var click = event => {
17
18
  event.stopPropagation();
18
- var copyText = JSON.stringify(text, (key, value) => {
19
+ var copyText = JSON.stringify(text || '', (key, value) => {
19
20
  if (typeof value === 'bigint') {
20
21
  return value.toString();
21
22
  }
@@ -23,6 +24,7 @@ export function Copied(props) {
23
24
  }, 2);
24
25
  if (text === Infinity) copyText = Infinity;
25
26
  navigator.clipboard.writeText(copyText).then(() => {
27
+ onCopied && onCopied(copyText, text);
26
28
  setCopied(true);
27
29
  var timer = setTimeout(() => {
28
30
  setCopied(false);
package/esm/index.d.ts CHANGED
@@ -17,6 +17,8 @@ export interface JsonViewProps<T extends object> extends React.DetailedHTMLProps
17
17
  keyName?: string | number;
18
18
  /** The user can copy objects and arrays to clipboard by clicking on the clipboard icon. @default true */
19
19
  enableClipboard?: boolean;
20
+ /** Whether to highlight updates. @default true */
21
+ highlightUpdates?: boolean;
20
22
  /** Display for quotes in object-key @default " */
21
23
  quotes?: "'" | '"' | '';
22
24
  /** When set to true, all nodes will be collapsed by default. Use an integer value to collapse at a particular depth. @default false */
@@ -28,6 +30,8 @@ export interface JsonViewProps<T extends object> extends React.DetailedHTMLProps
28
30
  keyid: string;
29
31
  keyName?: string | number;
30
32
  }) => void;
33
+ /** Fires event when you copy */
34
+ onCopied?: CopiedProps<T>['onCopied'];
31
35
  /** Redefine interface elements to re-render. */
32
36
  components?: {
33
37
  braces?: MetaProps['render'];
package/esm/node.d.ts CHANGED
@@ -6,6 +6,7 @@ export interface MetaProps extends LabelProps {
6
6
  start?: boolean;
7
7
  render?: (props: Pick<MetaProps, 'start' | 'isArray' | 'className' | 'children'>) => JSX.Element;
8
8
  }
9
+ export declare function usePrevious<T>(value: T): T | undefined;
9
10
  export declare function Meta(props: MetaProps): JSX.Element;
10
11
  export interface EllipsisProps extends React.HTMLAttributes<HTMLSpanElement> {
11
12
  render?: (props: EllipsisProps) => JSX.Element;
@@ -13,6 +14,7 @@ export interface EllipsisProps extends React.HTMLAttributes<HTMLSpanElement> {
13
14
  export declare const Ellipsis: FC<PropsWithChildren<EllipsisProps>>;
14
15
  export interface SemicolonProps extends LabelProps {
15
16
  show?: boolean;
17
+ highlightUpdates?: boolean;
16
18
  quotes?: JsonViewProps<object>['quotes'];
17
19
  value?: object;
18
20
  render?: (props: Omit<SemicolonProps, 'show'> & {}) => JSX.Element;
package/esm/node.js CHANGED
@@ -2,15 +2,22 @@ import _extends from "@babel/runtime/helpers/extends";
2
2
  import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/objectWithoutPropertiesLoose";
3
3
  var _excluded = ["isArray", "start", "className", "render"],
4
4
  _excluded2 = ["style", "render"],
5
- _excluded3 = ["children", "render", "color", "value", "className", "show", "quotes"],
6
- _excluded4 = ["value", "keyName", "displayDataTypes", "components", "displayObjectSize", "enableClipboard", "indentWidth", "collapsed", "level", "keyid", "quotes", "onExpand"];
7
- import { Fragment, useId, cloneElement, useState } from 'react';
5
+ _excluded3 = ["children", "render", "color", "value", "className", "show", "highlightUpdates", "quotes"],
6
+ _excluded4 = ["value", "keyName", "className", "displayDataTypes", "components", "displayObjectSize", "enableClipboard", "highlightUpdates", "indentWidth", "collapsed", "level", "keyid", "quotes", "onCopied", "onExpand"];
7
+ import { Fragment, useId, cloneElement, useState, useMemo, useRef, useEffect } from 'react';
8
8
  import { ValueView, Colon, Label, Line, typeMap } from './value';
9
9
  import { TriangleArrow } from './arrow/TriangleArrow';
10
10
  import { useExpandsStatus, store } from './store';
11
11
  import { Copied } from './copied';
12
12
  import { jsx as _jsx } from "react/jsx-runtime";
13
13
  import { jsxs as _jsxs } from "react/jsx-runtime";
14
+ export function usePrevious(value) {
15
+ var ref = useRef();
16
+ useEffect(() => {
17
+ ref.current = value;
18
+ });
19
+ return ref.current;
20
+ }
14
21
  export function Meta(props) {
15
22
  var {
16
23
  isArray = false,
@@ -67,9 +74,51 @@ export var Semicolon = _ref2 => {
67
74
  value,
68
75
  className = 'w-rjv-object-key',
69
76
  show,
77
+ highlightUpdates,
70
78
  quotes
71
79
  } = _ref2,
72
80
  props = _objectWithoutPropertiesLoose(_ref2, _excluded3);
81
+ var prevValue = usePrevious(value);
82
+ var highlightContainer = useRef(null);
83
+ var isHighlight = useMemo(() => {
84
+ if (!highlightUpdates || prevValue === undefined) return false;
85
+ // highlight if value type changed
86
+ if (typeof value !== typeof prevValue) {
87
+ return true;
88
+ }
89
+ if (typeof value === 'number') {
90
+ // notice: NaN !== NaN
91
+ if (isNaN(value) && isNaN(prevValue)) return false;
92
+ return value !== prevValue;
93
+ }
94
+ // highlight if isArray changed
95
+ if (Array.isArray(value) !== Array.isArray(prevValue)) {
96
+ return true;
97
+ }
98
+ // not highlight object/function
99
+ // deep compare they will be slow
100
+ if (typeof value === 'object' || typeof value === 'function') {
101
+ return false;
102
+ }
103
+
104
+ // highlight if not equal
105
+ if (value !== prevValue) {
106
+ return true;
107
+ }
108
+ return false;
109
+ }, [highlightUpdates, value]);
110
+ useEffect(() => {
111
+ if (highlightContainer.current && isHighlight && 'animate' in highlightContainer.current) {
112
+ highlightContainer.current.animate([{
113
+ backgroundColor: 'var(--w-rjv-update-color, #ebcb8b)'
114
+ }, {
115
+ backgroundColor: ''
116
+ }], {
117
+ duration: 1000,
118
+ easing: 'ease-in'
119
+ });
120
+ }
121
+ }, [isHighlight, value]);
73
122
  var content = show ? "" + quotes + children + quotes : children;
74
123
  if (render) return render(_extends({
75
124
  className
@@ -84,6 +133,7 @@ export var Semicolon = _ref2 => {
84
133
  className: className,
85
134
  color: color
86
135
  }, props, {
136
+ ref: highlightContainer,
87
137
  children: content
88
138
  }));
89
139
  };
@@ -97,7 +147,7 @@ export var CountInfo = _ref3 => {
97
147
  fontStyle: 'italic'
98
148
  },
99
149
  color: "var(--w-rjv-info-color, #0000004d)",
100
- className: "object-size",
150
+ className: "w-rjv-object-size",
101
151
  children: children
102
152
  });
103
153
  };
@@ -106,15 +156,18 @@ export function RooNode(props) {
106
156
  var {
107
157
  value = {},
108
158
  keyName,
159
+ className,
109
160
  displayDataTypes = true,
110
161
  components = {},
111
162
  displayObjectSize = true,
112
163
  enableClipboard = true,
164
+ highlightUpdates = true,
113
165
  indentWidth = 15,
114
166
  collapsed,
115
167
  level = 1,
116
168
  keyid = 'root',
117
169
  quotes = '"',
170
+ onCopied,
118
171
  onExpand
119
172
  } = props,
120
173
  reset = _objectWithoutPropertiesLoose(props, _excluded4);
@@ -142,6 +195,8 @@ export function RooNode(props) {
142
195
  displayDataTypes,
143
196
  displayObjectSize,
144
197
  enableClipboard,
198
+ highlightUpdates,
199
+ onCopied,
145
200
  onExpand,
146
201
  collapsed,
147
202
  quotes,
@@ -155,18 +210,22 @@ export function RooNode(props) {
155
210
  displayObjectSize,
156
211
  enableClipboard,
157
212
  indentWidth,
213
+ renderBraces: components.braces,
158
214
  renderValue: components.value
159
215
  };
160
216
  var arrowView = components.arrow ? /*#__PURE__*/cloneElement(components.arrow, {
161
217
  style: arrowStyle,
162
- 'data-expand': expand
218
+ 'data-expand': expand,
219
+ className: "w-rjv-arrow"
163
220
  }) : /*#__PURE__*/_jsx(TriangleArrow, {
164
- style: arrowStyle
221
+ style: arrowStyle,
222
+ className: "w-rjv-arrow"
165
223
  });
166
224
  var [showTools, setShowTools] = useState(false);
167
225
  var tools = enableClipboard ? /*#__PURE__*/_jsx(Copied, {
168
226
  show: showTools,
169
227
  text: value,
228
+ onCopied: onCopied,
170
229
  render: components.copied
171
230
  }) : undefined;
172
231
  var eventProps = {};
@@ -174,7 +233,9 @@ export function RooNode(props) {
174
233
  eventProps.onMouseEnter = () => setShowTools(true);
175
234
  eventProps.onMouseLeave = () => setShowTools(false);
176
235
  }
177
- return /*#__PURE__*/_jsxs("div", _extends({}, reset, eventProps, {
236
+ return /*#__PURE__*/_jsxs("div", _extends({}, reset, {
237
+ className: className + " w-rjv-inner"
238
+ }, eventProps, {
178
239
  children: [/*#__PURE__*/_jsxs(Line, {
179
240
  style: {
180
241
  display: 'inline-flex',
@@ -203,6 +264,7 @@ export function RooNode(props) {
203
264
  children: [nameKeys.length, " items"]
204
265
  }), tools]
205
266
  }), expand && /*#__PURE__*/_jsx(Line, {
267
+ className: "w-rjv-content",
206
268
  style: {
207
269
  borderLeft: 'var(--w-rjv-border-left-width, 1px) solid var(--w-rjv-line-color, #ebebeb)',
208
270
  marginLeft: 6
@@ -212,6 +274,7 @@ export function RooNode(props) {
212
274
  var renderKey = /*#__PURE__*/_jsx(Semicolon, {
213
275
  value: item,
214
276
  quotes: quotes,
277
+ highlightUpdates: highlightUpdates,
215
278
  render: components.objectKey,
216
279
  color: typeof key === 'number' ? typeMap['number'].color : '',
217
280
  show: typeof key === 'string',
@@ -221,6 +284,7 @@ export function RooNode(props) {
221
284
  if (Array.isArray(item) && !isEmpty) {
222
285
  var label = isArray ? idx : key;
223
286
  return /*#__PURE__*/_jsx(Line, {
287
+ className: "w-rjv-wrap",
224
288
  children: /*#__PURE__*/_jsx(RooNode, _extends({
225
289
  value: item,
226
290
  keyid: keyid + subkeyid + label,
@@ -230,6 +294,7 @@ export function RooNode(props) {
230
294
  }
231
295
  if (typeof item === 'object' && item && !(item instanceof Date) && !isEmpty) {
232
296
  return /*#__PURE__*/_jsx(Line, {
297
+ className: "w-rjv-wrap",
233
298
  children: /*#__PURE__*/_jsx(RooNode, _extends({
234
299
  keyid: keyid + subkeyid + key,
235
300
  value: item,
@@ -241,7 +306,6 @@ export function RooNode(props) {
241
306
  return;
242
307
  }
243
308
  return /*#__PURE__*/_jsx(ValueView, _extends({}, valueViewProps, {
244
- renderBraces: components.braces,
245
309
  renderKey: renderKey,
246
310
  keyName: key,
247
311
  value: item
@@ -5,6 +5,7 @@ export declare const darkTheme: {
5
5
  '--w-rjv-line-color': string;
6
6
  '--w-rjv-arrow-color': string;
7
7
  '--w-rjv-info-color': string;
8
+ '--w-rjv-update-color': string;
8
9
  '--w-rjv-copied-color': string;
9
10
  '--w-rjv-copied-success-color': string;
10
11
  '--w-rjv-curlybraces-color': string;
package/esm/theme/dark.js CHANGED
@@ -5,6 +5,7 @@ export var darkTheme = {
5
5
  '--w-rjv-line-color': '#323232',
6
6
  '--w-rjv-arrow-color': 'var(--w-rjv-color)',
7
7
  '--w-rjv-info-color': '#656565',
8
+ '--w-rjv-update-color': '#ebcb8b',
8
9
  '--w-rjv-copied-color': '#0184a6',
9
10
  '--w-rjv-copied-success-color': '#28a745',
10
11
  '--w-rjv-curlybraces-color': '#1896b6',
@@ -5,6 +5,7 @@ export declare const lightTheme: {
5
5
  '--w-rjv-line-color': string;
6
6
  '--w-rjv-arrow-color': string;
7
7
  '--w-rjv-info-color': string;
8
+ '--w-rjv-update-color': string;
8
9
  '--w-rjv-copied-color': string;
9
10
  '--w-rjv-copied-success-color': string;
10
11
  '--w-rjv-curlybraces-color': string;
@@ -5,6 +5,7 @@ export var lightTheme = {
5
5
  '--w-rjv-line-color': '#ebebeb',
6
6
  '--w-rjv-arrow-color': 'var(--w-rjv-color)',
7
7
  '--w-rjv-info-color': '#0000004d',
8
+ '--w-rjv-update-color': '#ebcb8b',
8
9
  '--w-rjv-copied-color': '#002b36',
9
10
  '--w-rjv-copied-success-color': '#28a745',
10
11
  '--w-rjv-curlybraces-color': '#236a7c',
package/esm/value.d.ts CHANGED
@@ -60,7 +60,7 @@ export interface LabelProps extends React.HTMLAttributes<HTMLSpanElement> {
60
60
  opacity?: number;
61
61
  paddingRight?: number;
62
62
  }
63
- export declare const Label: FC<PropsWithChildren<LabelProps>>;
63
+ export declare const Label: import("react").ForwardRefExoticComponent<LabelProps & import("react").RefAttributes<HTMLSpanElement>>;
64
64
  export interface TypeProps extends React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement> {
65
65
  type: keyof typeof typeMap;
66
66
  }
package/esm/value.js CHANGED
@@ -3,7 +3,7 @@ import _extends from "@babel/runtime/helpers/extends";
3
3
  var _excluded = ["children", "style"],
4
4
  _excluded2 = ["value", "keyName", "indentWidth", "renderKey", "renderValue", "renderBraces", "enableClipboard", "displayObjectSize", "displayDataTypes"],
5
5
  _excluded3 = ["children", "color", "fontSize", "opacity", "paddingRight", "style"];
6
- import { Fragment, useState } from 'react';
6
+ import { Fragment, forwardRef, useState } from 'react';
7
7
  import { Meta, CountInfo } from './node';
8
8
  import { Copied } from './copied';
9
9
  import { jsx as _jsx } from "react/jsx-runtime";
@@ -124,7 +124,12 @@ export function ValueView(props) {
124
124
  show: showTools,
125
125
  text: value
126
126
  }) : undefined;
127
- var eventProps = {};
127
+ var eventProps = {
128
+ className: 'w-rjv-line',
129
+ style: {
130
+ paddingLeft: indentWidth
131
+ }
132
+ };
128
133
  if (enableClipboard) {
129
134
  eventProps.onMouseEnter = () => setShowTools(true);
130
135
  eventProps.onMouseLeave = () => setShowTools(false);
@@ -145,12 +150,9 @@ export function ValueView(props) {
145
150
  className: "w-rjv-value",
146
151
  children: content
147
152
  });
148
- return /*#__PURE__*/_jsx(Line, _extends({
149
- style: {
150
- paddingLeft: indentWidth
151
- }
152
- }, eventProps, {
153
+ return /*#__PURE__*/_jsx(Line, _extends({}, eventProps, {
153
154
  children: /*#__PURE__*/_jsxs(Label, _extends({}, reset, {
155
+ ref: null,
154
156
  children: [renderKey, /*#__PURE__*/_jsx(Colon, {}), typeView, valueView, tools]
155
157
  }))
156
158
  }));
@@ -168,17 +170,14 @@ export function ValueView(props) {
168
170
  children: [length, " items"]
169
171
  })]
170
172
  });
171
- return /*#__PURE__*/_jsx(Line, _extends({
172
- style: {
173
- paddingLeft: indentWidth
174
- }
175
- }, eventProps, {
173
+ return /*#__PURE__*/_jsx(Line, _extends({}, eventProps, {
176
174
  children: /*#__PURE__*/_jsxs(Label, _extends({}, reset, {
175
+ ref: null,
177
176
  children: [renderKey, /*#__PURE__*/_jsx(Colon, {}), typeView, empty, tools]
178
177
  }))
179
178
  }));
180
179
  }
181
- export var Label = _ref2 => {
180
+ export var Label = /*#__PURE__*/forwardRef((_ref2, ref) => {
182
181
  var {
183
182
  children,
184
183
  color,
@@ -196,9 +195,10 @@ export var Label = _ref2 => {
196
195
  paddingRight
197
196
  }, style)
198
197
  }, reset, {
198
+ ref: ref,
199
199
  children: children
200
200
  }));
201
- };
201
+ });
202
202
  export var Type = props => {
203
203
  var _typeMap$type2, _typeMap$type3;
204
204
  var {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uiw/react-json-view",
3
- "version": "1.1.1",
3
+ "version": "1.3.0",
4
4
  "description": "JSON viewer for react.",
5
5
  "main": "cjs/index.js",
6
6
  "module": "esm/index.js",
@@ -74,7 +74,10 @@
74
74
  "treeview"
75
75
  ],
76
76
  "devDependencies": {
77
+ "@testing-library/react": "^14.0.0",
78
+ "@types/react-test-renderer": "^18.0.0",
77
79
  "react": "^18.2.0",
78
- "react-dom": "^18.2.0"
80
+ "react-dom": "^18.2.0",
81
+ "react-test-renderer": "^18.2.0"
79
82
  }
80
83
  }