@uiw/react-json-view 1.2.0 → 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.
@@ -424,6 +485,8 @@ export interface JsonViewProps<T> extends React.DetailedHTMLProps<React.HTMLAttr
424
485
  keyName?: string | number;
425
486
  /** The user can copy objects and arrays to clipboard by clicking on the clipboard icon. @default true */
426
487
  enableClipboard?: boolean;
488
+ /** Whether to highlight updates. @default true */
489
+ highlightUpdates?: boolean;
427
490
  /** Display for quotes in object-key @default " */
428
491
  quotes?: "'" | '"' | '';
429
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,7 +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
+ onCopied?: (text: string, obj: T | '') => void;
6
6
  render?: (props: Omit<CopiedProps<T>, 'render'>) => JSX.Element;
7
7
  }
8
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 */
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", "onCopied", "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
  };
@@ -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,
@@ -161,6 +215,7 @@ function RooNode(props) {
161
215
  displayDataTypes: displayDataTypes,
162
216
  displayObjectSize: displayObjectSize,
163
217
  enableClipboard: enableClipboard,
218
+ highlightUpdates: highlightUpdates,
164
219
  onCopied: onCopied,
165
220
  onExpand: onExpand,
166
221
  collapsed: collapsed,
@@ -206,7 +261,7 @@ function RooNode(props) {
206
261
  };
207
262
  }
208
263
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", (0, _objectSpread2["default"])((0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, reset), {}, {
209
- className: "w-rjv-inner"
264
+ className: "".concat(className, " w-rjv-inner")
210
265
  }, eventProps), {}, {
211
266
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_value.Line, {
212
267
  style: {
@@ -246,6 +301,7 @@ function RooNode(props) {
246
301
  var renderKey = /*#__PURE__*/(0, _jsxRuntime.jsx)(Semicolon, {
247
302
  value: item,
248
303
  quotes: quotes,
304
+ highlightUpdates: highlightUpdates,
249
305
  render: components.objectKey,
250
306
  color: typeof key === 'number' ? _value.typeMap['number'].color : '',
251
307
  show: typeof key === 'string',
@@ -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
@@ -174,6 +174,7 @@ function ValueView(props) {
174
174
  });
175
175
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(Line, (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, eventProps), {}, {
176
176
  children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(Label, (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, reset), {}, {
177
+ ref: null,
177
178
  children: [renderKey, /*#__PURE__*/(0, _jsxRuntime.jsx)(Colon, {}), typeView, valueView, tools]
178
179
  }))
179
180
  }));
@@ -193,11 +194,12 @@ function ValueView(props) {
193
194
  });
194
195
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(Line, (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, eventProps), {}, {
195
196
  children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(Label, (0, _objectSpread2["default"])((0, _objectSpread2["default"])({}, reset), {}, {
197
+ ref: null,
196
198
  children: [renderKey, /*#__PURE__*/(0, _jsxRuntime.jsx)(Colon, {}), typeView, empty, tools]
197
199
  }))
198
200
  }));
199
201
  }
200
- var Label = function Label(_ref2) {
202
+ var Label = /*#__PURE__*/(0, _react.forwardRef)(function (_ref2, ref) {
201
203
  var children = _ref2.children,
202
204
  color = _ref2.color,
203
205
  fontSize = _ref2.fontSize,
@@ -213,9 +215,10 @@ var Label = function Label(_ref2) {
213
215
  paddingRight: paddingRight
214
216
  }, style)
215
217
  }, reset), {}, {
218
+ ref: ref,
216
219
  children: children
217
220
  }));
218
- };
221
+ });
219
222
  exports.Label = Label;
220
223
  var Type = function Type(props) {
221
224
  var _typeMap$type2, _typeMap$type3;
package/esm/copied.d.ts CHANGED
@@ -2,7 +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
+ onCopied?: (text: string, obj: T | '') => void;
6
6
  render?: (props: Omit<CopiedProps<T>, 'render'>) => JSX.Element;
7
7
  }
8
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 */
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", "onCopied", "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
  };
@@ -106,10 +156,12 @@ 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,
@@ -143,6 +195,7 @@ export function RooNode(props) {
143
195
  displayDataTypes,
144
196
  displayObjectSize,
145
197
  enableClipboard,
198
+ highlightUpdates,
146
199
  onCopied,
147
200
  onExpand,
148
201
  collapsed,
@@ -181,7 +234,7 @@ export function RooNode(props) {
181
234
  eventProps.onMouseLeave = () => setShowTools(false);
182
235
  }
183
236
  return /*#__PURE__*/_jsxs("div", _extends({}, reset, {
184
- className: "w-rjv-inner"
237
+ className: className + " w-rjv-inner"
185
238
  }, eventProps, {
186
239
  children: [/*#__PURE__*/_jsxs(Line, {
187
240
  style: {
@@ -221,6 +274,7 @@ export function RooNode(props) {
221
274
  var renderKey = /*#__PURE__*/_jsx(Semicolon, {
222
275
  value: item,
223
276
  quotes: quotes,
277
+ highlightUpdates: highlightUpdates,
224
278
  render: components.objectKey,
225
279
  color: typeof key === 'number' ? typeMap['number'].color : '',
226
280
  show: typeof key === 'string',
@@ -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";
@@ -152,6 +152,7 @@ export function ValueView(props) {
152
152
  });
153
153
  return /*#__PURE__*/_jsx(Line, _extends({}, eventProps, {
154
154
  children: /*#__PURE__*/_jsxs(Label, _extends({}, reset, {
155
+ ref: null,
155
156
  children: [renderKey, /*#__PURE__*/_jsx(Colon, {}), typeView, valueView, tools]
156
157
  }))
157
158
  }));
@@ -171,11 +172,12 @@ export function ValueView(props) {
171
172
  });
172
173
  return /*#__PURE__*/_jsx(Line, _extends({}, eventProps, {
173
174
  children: /*#__PURE__*/_jsxs(Label, _extends({}, reset, {
175
+ ref: null,
174
176
  children: [renderKey, /*#__PURE__*/_jsx(Colon, {}), typeView, empty, tools]
175
177
  }))
176
178
  }));
177
179
  }
178
- export var Label = _ref2 => {
180
+ export var Label = /*#__PURE__*/forwardRef((_ref2, ref) => {
179
181
  var {
180
182
  children,
181
183
  color,
@@ -193,9 +195,10 @@ export var Label = _ref2 => {
193
195
  paddingRight
194
196
  }, style)
195
197
  }, reset, {
198
+ ref: ref,
196
199
  children: children
197
200
  }));
198
- };
201
+ });
199
202
  export var Type = props => {
200
203
  var _typeMap$type2, _typeMap$type3;
201
204
  var {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uiw/react-json-view",
3
- "version": "1.2.0",
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
  }