Initial commit with Advoware proxy

This commit is contained in:
root
2025-10-19 14:57:07 +00:00
commit 273aa8b549
45771 changed files with 5534555 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Suni
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,210 @@
# [React18 JSON View](https://jv.yysuni.com/)
<p >
<a href="https://www.npmjs.com/package/react18-json-view" target="_blank">
<img src="https://img.shields.io/npm/v/react18-json-view.svg" />
</a>
<a href="https://www.npmjs.com/package/react18-json-view" target="_blank">
<img src="https://img.shields.io/npm/dm/react18-json-view.svg" />
</a>
<a href="https://github.com/YYsuni/react18-json-view/blob/main/LICENSE" target="_blank">
<img src="https://img.shields.io/npm/l/react18-json-view.svg">
</a>
</p>
React function component for displaying javascript arrays and JSON objects. Supports all JS types.
[Website](https://jv.yysuni.com/), [Storybook](https://react18-json-view.vercel.app/),[Online](https://json-view-online.vercel.app/)
![JSON View](sample.png 'JSON View')
## Installation
```bash
npm i react18-json-view
```
```bash
npm i react18-json-view@canary
```
## Usage
```tsx
import JsonView from 'react18-json-view'
import 'react18-json-view/src/style.css'
// If dark mode is needed, import `dark.css`.
// import 'react18-json-view/src/dark.css'
;<JsonView src={my_json_object} />
// If needed, you can use the internal stringify function.
// import { stringify } from 'react18-json-view'
```
### Props
| Name | Type | Default | Description |
| :------------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `src` | `JSON Object` | None | <div style="min-width: 200px"> This property contains your input JSON </div> |
| `className` | `string` | None | The CSS class name(s) to apply to the component. |
| `style` | `JSON Object` | None | An object containing custom style rules to apply to the component. |
| `dark` | `boolean` | `false` | Keep in dark mode (Don't forget to import `dark.css`) |
| `theme` | `default` \| `a11y` \| `github` \| `vscode` \| `atom`\|`winter-is-coming` | `'default'` | Color theme |
| `enableClipboard` | `boolean` | `true` | Whether enable clipboard feature. |
| `matchesURL` | `boolean` | `true` | Show the link icon if value is string and matches URL regex pattern. |
| `urlRegExp` | `RegExp` | `/^(((ht\|f)tps?):\/\/)?([^!@#$%^&*?.\s-]([^!@#$%^&*?.\s]{0,63}[^!@#$%^&*?.\s])?\.)+[a-z]{2,6}\/?/` | URL RegExp pattern. |
| `displaySize` | `boolean` \| `integer` \| 'collapsed' \| 'expanded' | `false` | Whether display the size of `Object`, `Array`. |
| `displayArrayIndex` | `boolean` | `true` | Whether display the index of `Array`. |
| `collapseStringsAfterLength` | `integer` | `99` | When an integer value is assigned, strings longer than that length will be truncated and indicated by an ellipsis. To expand or collapse the string content, simply click on the string value. |
| `customizeCollapseStringUI` | ` (str_show: string, truncated: boolean) => (JSX.Element \| string)` \| `string` | - | Customize the collapse string UI. |
| `ignoreLargeArray` | `boolean` | `false` | Prevent collapsing large array(length > 100) behavior since v0.2.7 |
| `collapseStringMode` | `'directly'` \| `'word'` \| `'address'` | `'directly'` | If the `word` is assigned, the collapsed length will be adjusted to fully display the last word. |
| `collapsed` | `boolean` \| `integer` \| `function` | `false` | When set to true(false), all nodes will be (not) collapsed by default. When using an integer value, it will collapse at a specific depth. The collapsed also can be a function. |
| `onCollapse` | `function` | - | `(params: { isCollapsing: boolean, node: Record<string, any> \| Array<any>, indexOrName: string \| number \| undefined, depth: number }) => void` |
| `collapseObjectsAfterLength` | `integer` | `99` | When an integer value is assigned, the object and array will initially collapse. |
| `editable` | `boolean` \| {add?: `boolean`, edit?: `boolean`, delete?: `boolean`} | `false` | When set to true, you can add, edit, or delete the property, and the actions will trigger onAdd, onEdit, or onDelete. Options is available. |
| `onAdd` | `function` | - | `(params: { indexOrName: string\| number, depth: number, src: any; parentType: 'object' \| 'array' }) => void` |
| `onDelete` | `function` | - | `(params:{ value: any,indexOrName: string \| number,depth: number,src: any,parentType: 'object' \| 'array'}) => void` |
| `onEdit` | `function` | - | `(params: { newValue: any, oldValue: any, depth: number, src: any, indexOrName: string \| number, parentType: 'object' \| 'array'}) => void` |
| `customizeNode` | `ReactElement`\|`ReactComponent`\|`Options` | - | Highly customize every node. |
| `customizeCopy` | `(node: any, nodeMeta: NodeMeta) => any` | internal stringify | Customize copy behavior, only the returned non-empty string will be written to clipboard. |
| `CopyComponent` \/ `DoneComponent` \/ `CancelComponent` | `React.FC` \/ `React.Component` `<{ onClick: (event: React.MouseEvent) => void; className: string ; style: React.CSSProperties}>` | - | Customize copy icon. |
| `CopiedComponent` | `React.FC` \/ `React.Component` `<{ className: string; style: React.CSSProperties }>` | - | Customize copied icon. |
| `CustomOperation` | `React.FC` \/ `React.Component` `<{ node: any }>` | - | Custom Operation |
### Collapsed function
```ts
;(params: {
node: Record<string, any> | Array<any> // Object or array
indexOrName: number | string | undefined
depth: number
size: number // Object's size or array's length
}) => boolean
```
### Editable options
```ts
{
add?: boolean
edit?: boolean
delete?: boolean
}
```
> onEdit, OnDelete, onAdd and OnChange add `parentPath` field, allow us to correctly indicate which field in the JSON is being edited.
### CustomizeNode
```ts
(params: { node: any; indexOrName: number | string | undefined; depth: number }) =>
| {
add?: boolean
edit?: boolean
delete?: boolean
enableClipboard?: boolean
collapsed?: boolean
className?: string
}
| React.FC
| typeof React.Component
| React.ReactElement<any, any>
```
### NodeMeta
```ts
{ depth: number; indexOrName?: number | string; parent?: Record<string, any> | Array<any>; parentPath: string[], currentPath: string[] }
```
## Editable
### How to generate object/array
The editor uses `JSON.parse(<input-value>)`. While in edit mode, you can enter `({})` or `([])`, which will cause the result of eval to become a new object or array.
> `{}` and `[]` will be auto convert to `({})`,`([])`
### How the editor works
This component does not perform any cloning operations, so every step of the operation is carried out on the original object. If cloning is required, please handle it yourself.
### Edit keyboard shortcuts
When element is editable:
- `Ctrl/Cmd+Click` => Edit Mode
- `Enter` => Submit
- `Esc` => Cancel
## Custom themes
Below are the default theme variables that you can easily customize to fit your needs.
```css
.json-view {
color: #4d4d4d;
--json-property: #009033;
--json-index: #676dff;
--json-number: #676dff;
--json-string: #b2762e;
--json-boolean: #dc155e;
--json-null: #dc155e;
}
.json-view .json-view--property {
color: var(--json-property);
}
.json-view .json-view--index {
color: var(--json-index);
}
.json-view .json-view--number {
color: var(--json-number);
}
.json-view .json-view--string {
color: var(--json-string);
}
.json-view .json-view--boolean {
color: var(--json-boolean);
}
.json-view .json-view--null {
color: var(--json-null);
}
```
## Why
react-json-view does not support React 18.
## Todo
- [x] copy (enableClipboard)
- [x] css
- [x] collapse at a particular depth (collapsed)
- [x] editable
- [x] add
- [x] edit
- [x] delete
- [x] onChange
- [ ] onSelect
- [x] dark mode
- [ ] custom icon
- [x] export default icons
- [x] more usability/scenarios
- [ ] gif guide
- [x] more color themes(dark)
- [x] collapse objects callback
- [x] editable option
- [x] advance customization
- [ ] access internal actions
- [ ] map/set viewer
- [ ] display data type
- [x] display object size
- [ ] handle circle loop
- [x] redesign docs
- [x] truncate long strings
- [ ] custom `stringify`
- [x] split large array
* tree?

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,8 @@
/// <reference types="react" />
import { NodeMeta } from 'src/types';
interface Props {
node: any;
nodeMeta: NodeMeta;
}
export default function CopyButton({ node, nodeMeta }: Props): JSX.Element;
export {};

View File

@@ -0,0 +1,12 @@
/// <reference types="react" />
interface Props {
node: any;
depth: number;
deleteHandle?: (indexOrName: string | number, parentPath: string[]) => void;
editHandle?: (indexOrName: string | number, newValue: any, oldValue: any, parentPath: string[]) => void;
indexOrName?: number | string;
parent?: Record<string, any> | Array<any>;
parentPath: string[];
}
export default function JsonNode({ node, depth, deleteHandle: _deleteHandle, indexOrName, parent, editHandle, parentPath }: Props): JSX.Element;
export {};

View File

@@ -0,0 +1,180 @@
/// <reference types="react" />
import type { Collapsed, CustomizeCollapseStringUI, CustomizeNode, DisplaySize, Editable, NodeMeta } from '../types';
type OnEdit = (params: {
newValue: any;
oldValue: any;
depth: number;
src: any;
indexOrName: string | number;
parentType: 'object' | 'array' | null;
parentPath: string[];
}) => void;
type OnDelete = (params: {
value: any;
indexOrName: string | number;
depth: number;
src: any;
parentType: 'object' | 'array' | null;
parentPath: string[];
}) => void;
type OnAdd = (params: {
indexOrName: string | number;
depth: number;
src: any;
parentType: 'object' | 'array';
parentPath: string[];
}) => void;
type OnChange = (params: {
indexOrName: string | number;
depth: number;
src: any;
parentType: 'object' | 'array' | null;
type: 'add' | 'edit' | 'delete';
parentPath: string[];
}) => void;
type OnCollapse = (params: {
isCollapsing: boolean;
node: Record<string, any> | Array<any>;
indexOrName: string | number | undefined;
depth: number;
}) => void;
export declare const defaultURLRegExp: RegExp;
export declare const JsonViewContext: import("react").Context<{
src: any;
collapseStringsAfterLength: number;
collapseStringMode: "directly" | "word" | "address";
customizeCollapseStringUI: CustomizeCollapseStringUI | undefined;
collapseObjectsAfterLength: number;
collapsed: Collapsed;
onCollapse: OnCollapse | undefined;
enableClipboard: boolean;
editable: Editable;
onEdit: OnEdit | undefined;
onDelete: OnDelete | undefined;
onAdd: OnAdd | undefined;
onChange: OnChange | undefined;
forceUpdate: () => void;
customizeNode: CustomizeNode | undefined;
customizeCopy: (node: any, nodeMeta?: NodeMeta) => any;
displaySize: DisplaySize;
displayArrayIndex: boolean;
matchesURL: boolean;
urlRegExp: RegExp;
ignoreLargeArray: boolean;
CopyComponent: import("react").FC<{
onClick: (event: React.MouseEvent) => void;
className: string;
}> | import("react").Component<{
onClick: (event: React.MouseEvent) => void;
className: string;
}, {}, any> | undefined;
CopiedComponent: import("react").FC<{
className: string;
style: React.CSSProperties;
}> | import("react").Component<{
className: string;
style: React.CSSProperties;
}, {}, any> | undefined;
EditComponent: import("react").FC<{
onClick: (event: React.MouseEvent) => void;
className: string;
}> | import("react").Component<{
onClick: (event: React.MouseEvent) => void;
className: string;
}, {}, any> | undefined;
CancelComponent: import("react").FC<{
onClick: (event: React.MouseEvent) => void;
className: string;
style: React.CSSProperties;
}> | import("react").Component<{
onClick: (event: React.MouseEvent) => void;
className: string;
style: React.CSSProperties;
}, {}, any> | undefined;
DoneComponent: import("react").FC<{
onClick: (event: React.MouseEvent) => void;
className: string;
style: React.CSSProperties;
}> | import("react").Component<{
onClick: (event: React.MouseEvent) => void;
className: string;
style: React.CSSProperties;
}, {}, any> | undefined;
CustomOperation: import("react").FC<{
node: any;
}> | import("react").Component<{
node: any;
}, {}, any> | undefined;
}>;
export interface JsonViewProps {
src: any;
collapseStringsAfterLength?: number;
collapseStringMode?: 'directly' | 'word' | 'address';
customizeCollapseStringUI?: CustomizeCollapseStringUI;
collapseObjectsAfterLength?: number;
collapsed?: Collapsed;
onCollapse?: OnCollapse;
enableClipboard?: boolean;
editable?: Editable;
onEdit?: OnEdit;
onDelete?: OnDelete;
onAdd?: OnAdd;
onChange?: OnChange;
customizeNode?: CustomizeNode;
customizeCopy?: (node: any, nodeMeta?: NodeMeta) => any;
dark?: boolean;
theme?: 'default' | 'a11y' | 'github' | 'vscode' | 'atom' | 'winter-is-coming';
displaySize?: DisplaySize;
displayArrayIndex?: boolean;
style?: React.CSSProperties;
className?: string;
matchesURL?: boolean;
urlRegExp?: RegExp;
ignoreLargeArray?: boolean;
CopyComponent?: React.FC<{
onClick: (event: React.MouseEvent) => void;
className: string;
}> | React.Component<{
onClick: (event: React.MouseEvent) => void;
className: string;
}>;
CopiedComponent?: React.FC<{
className: string;
style: React.CSSProperties;
}> | React.Component<{
className: string;
style: React.CSSProperties;
}>;
EditComponent?: React.FC<{
onClick: (event: React.MouseEvent) => void;
className: string;
}> | React.Component<{
onClick: (event: React.MouseEvent) => void;
className: string;
}>;
CancelComponent?: React.FC<{
onClick: (event: React.MouseEvent) => void;
className: string;
style: React.CSSProperties;
}> | React.Component<{
onClick: (event: React.MouseEvent) => void;
className: string;
style: React.CSSProperties;
}>;
DoneComponent?: React.FC<{
onClick: (event: React.MouseEvent) => void;
className: string;
style: React.CSSProperties;
}> | React.Component<{
onClick: (event: React.MouseEvent) => void;
className: string;
style: React.CSSProperties;
}>;
CustomOperation?: React.FC<{
node: any;
}> | React.Component<{
node: any;
}>;
}
export default function JsonView({ src: _src, collapseStringsAfterLength, collapseStringMode, customizeCollapseStringUI, collapseObjectsAfterLength, collapsed, onCollapse, enableClipboard, editable, onEdit, onDelete, onAdd, onChange, dark, theme, customizeNode, customizeCopy, displaySize, displayArrayIndex, style, className, matchesURL, urlRegExp, ignoreLargeArray, CopyComponent, CopiedComponent, EditComponent, CancelComponent, DoneComponent, CustomOperation }: JsonViewProps): JSX.Element;
export {};

View File

@@ -0,0 +1,15 @@
/// <reference types="react" />
import type { CustomizeOptions } from '../types';
interface Props {
originNode: Array<any>;
node: Array<any>;
depth: number;
index: number;
deleteHandle?: (_: string | number, currentPath: string[]) => void;
customOptions?: CustomizeOptions;
startIndex: number;
parent?: Record<string, any> | Array<any>;
parentPath: string[];
}
export default function LargeArrayNode({ originNode, node, depth, index, deleteHandle: _deleteSelf, customOptions, startIndex, parent, parentPath }: Props): JSX.Element;
export {};

View File

@@ -0,0 +1,13 @@
/// <reference types="react" />
import type { CustomizeOptions } from '../types';
interface Props {
node: Array<any>;
depth: number;
indexOrName?: number | string;
deleteHandle?: (_: string | number, currentPath: string[]) => void;
customOptions?: CustomizeOptions;
parent?: Record<string, any> | Array<any>;
parentPath: string[];
}
export default function LargeArray({ node, depth, deleteHandle: _deleteSelf, indexOrName, customOptions, parent, parentPath }: Props): JSX.Element;
export {};

View File

@@ -0,0 +1,8 @@
import React from 'react';
interface Props {
str: string;
className: string;
ctrlClick: ((event: React.MouseEvent) => void) | undefined;
}
declare const LongString: React.ForwardRefExoticComponent<Props & React.RefAttributes<HTMLSpanElement>>;
export default LongString;

View File

@@ -0,0 +1,12 @@
/// <reference types="react" />
interface Props {
indexOrName: number | string;
value: any;
depth: number;
parent?: Record<string, any> | Array<any>;
parentPath: string[];
deleteHandle: (indexOrName: string | number, parentPath: string[]) => void;
editHandle: (indexOrName: string | number, newValue: any, oldValue: any, parentPath: string[]) => void;
}
export default function NameValue({ indexOrName, value, depth, deleteHandle, editHandle, parent, parentPath }: Props): JSX.Element;
export {};

View File

@@ -0,0 +1,13 @@
/// <reference types="react" />
import type { CustomizeOptions } from '../types';
interface Props {
node: Record<string, any> | Array<any>;
depth: number;
indexOrName?: number | string;
deleteHandle?: (_: string | number, currentPath: string[]) => void;
customOptions?: CustomizeOptions;
parent?: Record<string, any> | Array<any>;
parentPath: string[];
}
export default function ObjectNode({ node, depth, indexOrName, deleteHandle: _deleteSelf, customOptions, parent, parentPath }: Props): JSX.Element;
export {};

View File

@@ -0,0 +1,994 @@
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
import * as React from 'react';
import React__default, { useContext, useState, useCallback, useEffect, useRef, isValidElement, useMemo, createContext } from 'react';
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
var toggleSelection = function () {
var selection = document.getSelection();
if (!selection.rangeCount) {
return function () {};
}
var active = document.activeElement;
var ranges = [];
for (var i = 0; i < selection.rangeCount; i++) {
ranges.push(selection.getRangeAt(i));
}
switch (active.tagName.toUpperCase()) { // .toUpperCase handles XHTML
case 'INPUT':
case 'TEXTAREA':
active.blur();
break;
default:
active = null;
break;
}
selection.removeAllRanges();
return function () {
selection.type === 'Caret' &&
selection.removeAllRanges();
if (!selection.rangeCount) {
ranges.forEach(function(range) {
selection.addRange(range);
});
}
active &&
active.focus();
};
};
var deselectCurrent = toggleSelection;
var clipboardToIE11Formatting = {
"text/plain": "Text",
"text/html": "Url",
"default": "Text"
};
var defaultMessage = "Copy to clipboard: #{key}, Enter";
function format(message) {
var copyKey = (/mac os x/i.test(navigator.userAgent) ? "⌘" : "Ctrl") + "+C";
return message.replace(/#{\s*key\s*}/g, copyKey);
}
function copy(text, options) {
var debug,
message,
reselectPrevious,
range,
selection,
mark,
success = false;
if (!options) {
options = {};
}
debug = options.debug || false;
try {
reselectPrevious = deselectCurrent();
range = document.createRange();
selection = document.getSelection();
mark = document.createElement("span");
mark.textContent = text;
// avoid screen readers from reading out loud the text
mark.ariaHidden = "true";
// reset user styles for span element
mark.style.all = "unset";
// prevents scrolling to the end of the page
mark.style.position = "fixed";
mark.style.top = 0;
mark.style.clip = "rect(0, 0, 0, 0)";
// used to preserve spaces and line breaks
mark.style.whiteSpace = "pre";
// do not inherit user-select (it may be `none`)
mark.style.webkitUserSelect = "text";
mark.style.MozUserSelect = "text";
mark.style.msUserSelect = "text";
mark.style.userSelect = "text";
mark.addEventListener("copy", function(e) {
e.stopPropagation();
if (options.format) {
e.preventDefault();
if (typeof e.clipboardData === "undefined") { // IE 11
debug && console.warn("unable to use e.clipboardData");
debug && console.warn("trying IE specific stuff");
window.clipboardData.clearData();
var format = clipboardToIE11Formatting[options.format] || clipboardToIE11Formatting["default"];
window.clipboardData.setData(format, text);
} else { // all other browsers
e.clipboardData.clearData();
e.clipboardData.setData(options.format, text);
}
}
if (options.onCopy) {
e.preventDefault();
options.onCopy(e.clipboardData);
}
});
document.body.appendChild(mark);
range.selectNodeContents(mark);
selection.addRange(range);
var successful = document.execCommand("copy");
if (!successful) {
throw new Error("copy command was unsuccessful");
}
success = true;
} catch (err) {
debug && console.error("unable to copy using execCommand: ", err);
debug && console.warn("trying IE specific stuff");
try {
window.clipboardData.setData(options.format || "text", text);
options.onCopy && options.onCopy(window.clipboardData);
success = true;
} catch (err) {
debug && console.error("unable to copy using clipboardData: ", err);
debug && console.error("falling back to prompt");
message = format("message" in options ? options.message : defaultMessage);
window.prompt(message, text);
}
} finally {
if (selection) {
if (typeof selection.removeRange == "function") {
selection.removeRange(range);
} else {
selection.removeAllRanges();
}
}
if (mark) {
document.body.removeChild(mark);
}
reselectPrevious();
}
return success;
}
var copyToClipboard = copy;
function isObject(node) {
return Object.prototype.toString.call(node) === '[object Object]';
}
function objectSize(node) {
return Array.isArray(node) ? node.length : isObject(node) ? Object.keys(node).length : 0;
}
function stringifyForCopying(node, space) {
// return single string nodes without quotes
if (typeof node === 'string') {
return node;
}
try {
return JSON.stringify(node, (key, value) => {
switch (typeof value) {
case 'bigint':
return String(value) + 'n';
case 'number':
case 'boolean':
case 'object':
case 'string':
return value;
default:
return String(value);
}
}, space);
}
catch (error) {
return `${error.name}: ${error.message}` || 'JSON.stringify failed';
}
}
function writeClipboard(value) {
return __awaiter(this, void 0, void 0, function* () {
try {
yield navigator.clipboard.writeText(value);
}
catch (err) {
copyToClipboard(value);
}
});
}
function isCollapsed(node, depth, indexOrName, collapsed, collapseObjectsAfterLength, customOptions) {
if (customOptions && customOptions.collapsed !== undefined)
return !!customOptions.collapsed;
if (typeof collapsed === 'boolean')
return collapsed;
if (typeof collapsed === 'number' && depth > collapsed)
return true;
const size = objectSize(node);
if (typeof collapsed === 'function') {
const result = safeCall(collapsed, [{ node, depth, indexOrName, size }]);
if (typeof result === 'boolean')
return result;
}
if (Array.isArray(node) && size > collapseObjectsAfterLength)
return true;
if (isObject(node) && size > collapseObjectsAfterLength)
return true;
return false;
}
function isCollapsed_largeArray(node, depth, indexOrName, collapsed, collapseObjectsAfterLength, customOptions) {
if (customOptions && customOptions.collapsed !== undefined)
return !!customOptions.collapsed;
if (typeof collapsed === 'boolean')
return collapsed;
if (typeof collapsed === 'number' && depth > collapsed)
return true;
const size = Math.ceil(node.length / 100);
if (typeof collapsed === 'function') {
const result = safeCall(collapsed, [{ node, depth, indexOrName, size }]);
if (typeof result === 'boolean')
return result;
}
if (Array.isArray(node) && size > collapseObjectsAfterLength)
return true;
if (isObject(node) && size > collapseObjectsAfterLength)
return true;
return false;
}
function ifDisplay(displaySize, depth, fold) {
if (typeof displaySize === 'boolean')
return displaySize;
if (typeof displaySize === 'number' && depth > displaySize)
return true;
if (displaySize === 'collapsed' && fold)
return true;
if (displaySize === 'expanded' && !fold)
return true;
return false;
}
function safeCall(func, params) {
try {
return func(...params);
}
catch (event) {
reportError(event);
}
}
function editableAdd(editable) {
if (editable === true)
return true;
if (isObject(editable) && editable.add === true)
return true;
}
function editableEdit(editable) {
if (editable === true)
return true;
if (isObject(editable) && editable.edit === true)
return true;
}
function editableDelete(editable) {
if (editable === true)
return true;
if (isObject(editable) && editable.delete === true)
return true;
}
function isReactComponent(component) {
return typeof component === 'function';
}
function customAdd(customOptions) {
return !customOptions || customOptions.add === undefined || !!customOptions.add;
}
function customEdit(customOptions) {
return !customOptions || customOptions.edit === undefined || !!customOptions.edit;
}
function customDelete(customOptions) {
return !customOptions || customOptions.delete === undefined || !!customOptions.delete;
}
function customCopy(customOptions) {
return !customOptions || customOptions.enableClipboard === undefined || !!customOptions.enableClipboard;
}
function customMatchesURL(customOptions) {
return !customOptions || customOptions.matchesURL === undefined || !!customOptions.matchesURL;
}
function resolveEvalFailedNewValue(type, value) {
if (type === 'string') {
return value.trim().replace(/^\"([\s\S]+?)\"$/, '$1');
}
return value;
}
var _path$8;
function _extends$8() { _extends$8 = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends$8.apply(this, arguments); }
var SvgAngleDown = function SvgAngleDown(props) {
return /*#__PURE__*/React.createElement("svg", _extends$8({
xmlns: "http://www.w3.org/2000/svg",
width: 16,
height: 16,
fill: "none",
viewBox: "0 0 16 16"
}, props), _path$8 || (_path$8 = /*#__PURE__*/React.createElement("path", {
fill: "currentColor",
d: "M12.473 5.806a.666.666 0 0 0-.946 0L8.473 8.86a.667.667 0 0 1-.946 0L4.473 5.806a.667.667 0 1 0-.946.94l3.06 3.06a2 2 0 0 0 2.826 0l3.06-3.06a.667.667 0 0 0 0-.94Z"
})));
};
var _path$7;
function _extends$7() { _extends$7 = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends$7.apply(this, arguments); }
var SvgCopy = function SvgCopy(props) {
return /*#__PURE__*/React.createElement("svg", _extends$7({
xmlns: "http://www.w3.org/2000/svg",
width: 24,
height: 24,
fill: "none",
viewBox: "0 0 24 24"
}, props), _path$7 || (_path$7 = /*#__PURE__*/React.createElement("path", {
fill: "currentColor",
d: "M17.542 2.5h-4.75a3.963 3.963 0 0 0-3.959 3.958v4.75a3.963 3.963 0 0 0 3.959 3.959h4.75a3.963 3.963 0 0 0 3.958-3.959v-4.75A3.963 3.963 0 0 0 17.542 2.5Zm2.375 8.708a2.378 2.378 0 0 1-2.375 2.375h-4.75a2.378 2.378 0 0 1-2.375-2.375v-4.75a2.378 2.378 0 0 1 2.375-2.375h4.75a2.378 2.378 0 0 1 2.375 2.375v4.75Zm-4.75 6.334a3.963 3.963 0 0 1-3.959 3.958h-4.75A3.963 3.963 0 0 1 2.5 17.542v-4.75a3.963 3.963 0 0 1 3.958-3.959.791.791 0 1 1 0 1.584 2.378 2.378 0 0 0-2.375 2.375v4.75a2.378 2.378 0 0 0 2.375 2.375h4.75a2.378 2.378 0 0 0 2.375-2.375.792.792 0 1 1 1.584 0Z"
})));
};
var _path$6, _path2$5;
function _extends$6() { _extends$6 = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends$6.apply(this, arguments); }
var SvgCopied = function SvgCopied(props) {
return /*#__PURE__*/React.createElement("svg", _extends$6({
xmlns: "http://www.w3.org/2000/svg",
width: 24,
height: 24,
fill: "none",
viewBox: "0 0 24 24"
}, props), _path$6 || (_path$6 = /*#__PURE__*/React.createElement("path", {
fill: "currentColor",
d: "M17.25 3H6.75A3.755 3.755 0 0 0 3 6.75v10.5A3.754 3.754 0 0 0 6.75 21h10.5A3.754 3.754 0 0 0 21 17.25V6.75A3.755 3.755 0 0 0 17.25 3Zm2.25 14.25a2.25 2.25 0 0 1-2.25 2.25H6.75a2.25 2.25 0 0 1-2.25-2.25V6.75A2.25 2.25 0 0 1 6.75 4.5h10.5a2.25 2.25 0 0 1 2.25 2.25v10.5Z"
})), _path2$5 || (_path2$5 = /*#__PURE__*/React.createElement("path", {
fill: "#14C786",
d: "M10.312 14.45 7.83 11.906a.625.625 0 0 0-.896 0 .659.659 0 0 0 0 .918l2.481 2.546a1.264 1.264 0 0 0 .896.381 1.237 1.237 0 0 0 .895-.38l5.858-6.011a.658.658 0 0 0 0-.919.625.625 0 0 0-.896 0l-5.857 6.01Z"
})));
};
function CopyButton({ node, nodeMeta }) {
const { customizeCopy, CopyComponent, CopiedComponent } = useContext(JsonViewContext);
const [copied, setCopied] = useState(false);
const copyHandler = (event) => {
event.stopPropagation();
const value = customizeCopy(node, nodeMeta);
if (typeof value === 'string' && value) {
writeClipboard(value);
}
setCopied(true);
setTimeout(() => setCopied(false), 3000);
};
return copied ? (typeof CopiedComponent === 'function' ? (jsx(CopiedComponent, { className: 'json-view--copy', style: { display: 'inline-block' } })) : (jsx(SvgCopied, { className: 'json-view--copy', style: { display: 'inline-block' } }))) : typeof CopyComponent === 'function' ? (jsx(CopyComponent, { onClick: copyHandler, className: 'json-view--copy' })) : (jsx(SvgCopy, { onClick: copyHandler, className: 'json-view--copy' }));
}
function NameValue({ indexOrName, value, depth, deleteHandle, editHandle, parent, parentPath }) {
const { displayArrayIndex } = useContext(JsonViewContext);
const isArray = Array.isArray(parent);
return (jsxs("div", Object.assign({ className: 'json-view--pair' }, { children: [!isArray || (isArray && displayArrayIndex) ? (jsxs(Fragment, { children: [jsx("span", Object.assign({ className: typeof indexOrName === 'number' ? 'json-view--index' : 'json-view--property' }, { children: indexOrName })), ":", ' '] })) : (jsx(Fragment, {})), jsx(JsonNode, { node: value, depth: depth + 1, deleteHandle: (indexOrName, parentPath) => deleteHandle(indexOrName, parentPath), editHandle: (indexOrName, newValue, oldValue, parentPath) => editHandle(indexOrName, newValue, oldValue, parentPath), parent: parent, indexOrName: indexOrName, parentPath: parentPath })] })));
}
var _path$5, _path2$4;
function _extends$5() { _extends$5 = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends$5.apply(this, arguments); }
var SvgTrash = function SvgTrash(props) {
return /*#__PURE__*/React.createElement("svg", _extends$5({
xmlns: "http://www.w3.org/2000/svg",
width: 24,
height: 24,
fill: "none",
viewBox: "0 0 24 24"
}, props), _path$5 || (_path$5 = /*#__PURE__*/React.createElement("path", {
fill: "currentColor",
d: "M18.75 6h-2.325a3.757 3.757 0 0 0-3.675-3h-1.5a3.757 3.757 0 0 0-3.675 3H5.25a.75.75 0 0 0 0 1.5H6v9.75A3.754 3.754 0 0 0 9.75 21h4.5A3.754 3.754 0 0 0 18 17.25V7.5h.75a.75.75 0 1 0 0-1.5Zm-7.5-1.5h1.5A2.255 2.255 0 0 1 14.872 6H9.128a2.255 2.255 0 0 1 2.122-1.5Zm5.25 12.75a2.25 2.25 0 0 1-2.25 2.25h-4.5a2.25 2.25 0 0 1-2.25-2.25V7.5h9v9.75Z"
})), _path2$4 || (_path2$4 = /*#__PURE__*/React.createElement("path", {
fill: "#DA0000",
d: "M10.5 16.5a.75.75 0 0 0 .75-.75v-4.5a.75.75 0 1 0-1.5 0v4.5a.75.75 0 0 0 .75.75ZM13.5 16.5a.75.75 0 0 0 .75-.75v-4.5a.75.75 0 1 0-1.5 0v4.5a.75.75 0 0 0 .75.75Z"
})));
};
var _path$4, _path2$3;
function _extends$4() { _extends$4 = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends$4.apply(this, arguments); }
var SvgAddSquare = function SvgAddSquare(props) {
return /*#__PURE__*/React.createElement("svg", _extends$4({
xmlns: "http://www.w3.org/2000/svg",
width: 24,
height: 24,
fill: "none",
viewBox: "0 0 24 24"
}, props), _path$4 || (_path$4 = /*#__PURE__*/React.createElement("path", {
fill: "currentColor",
d: "M21 6.75v10.5A3.754 3.754 0 0 1 17.25 21H6.75A3.754 3.754 0 0 1 3 17.25V6.75A3.754 3.754 0 0 1 6.75 3h10.5A3.754 3.754 0 0 1 21 6.75Zm-1.5 0c0-1.24-1.01-2.25-2.25-2.25H6.75C5.51 4.5 4.5 5.51 4.5 6.75v10.5c0 1.24 1.01 2.25 2.25 2.25h10.5c1.24 0 2.25-1.01 2.25-2.25V6.75Z"
})), _path2$3 || (_path2$3 = /*#__PURE__*/React.createElement("path", {
fill: "#14C786",
d: "M15 12.75a.75.75 0 1 0 0-1.5h-2.25V9a.75.75 0 1 0-1.5 0v2.25H9a.75.75 0 1 0 0 1.5h2.25V15a.75.75 0 1 0 1.5 0v-2.25H15Z"
})));
};
var _path$3, _path2$2;
function _extends$3() { _extends$3 = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends$3.apply(this, arguments); }
var SvgDone = function SvgDone(props) {
return /*#__PURE__*/React.createElement("svg", _extends$3({
xmlns: "http://www.w3.org/2000/svg",
width: 24,
height: 24,
fill: "none",
viewBox: "0 0 24 24"
}, props), _path$3 || (_path$3 = /*#__PURE__*/React.createElement("path", {
fill: "currentColor",
d: "M12 3a9 9 0 1 0 9 9 9.01 9.01 0 0 0-9-9Zm0 16.5a7.5 7.5 0 1 1 7.5-7.5 7.509 7.509 0 0 1-7.5 7.5Z"
})), _path2$2 || (_path2$2 = /*#__PURE__*/React.createElement("path", {
fill: "#14C786",
d: "m10.85 13.96-1.986-2.036a.5.5 0 0 0-.716 0 .527.527 0 0 0 0 .735l1.985 2.036a1.01 1.01 0 0 0 .717.305.99.99 0 0 0 .716-.305l4.686-4.808a.526.526 0 0 0 0-.735.5.5 0 0 0-.716 0l-4.687 4.809Z"
})));
};
var _path$2, _path2$1;
function _extends$2() { _extends$2 = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends$2.apply(this, arguments); }
var SvgCancel = function SvgCancel(props) {
return /*#__PURE__*/React.createElement("svg", _extends$2({
xmlns: "http://www.w3.org/2000/svg",
width: 24,
height: 24,
fill: "none",
viewBox: "0 0 24 24"
}, props), _path$2 || (_path$2 = /*#__PURE__*/React.createElement("path", {
fill: "#DA0000",
d: "M15 9a.75.75 0 0 0-1.06 0L12 10.94 10.06 9A.75.75 0 0 0 9 10.06L10.94 12 9 13.94A.75.75 0 0 0 10.06 15L12 13.06 13.94 15A.75.75 0 0 0 15 13.94L13.06 12 15 10.06A.75.75 0 0 0 15 9Z"
})), _path2$1 || (_path2$1 = /*#__PURE__*/React.createElement("path", {
fill: "currentColor",
d: "M12 3a9 9 0 1 0 9 9 9.01 9.01 0 0 0-9-9Zm0 16.5a7.5 7.5 0 1 1 7.5-7.5 7.509 7.509 0 0 1-7.5 7.5Z"
})));
};
function LargeArrayNode({ originNode, node, depth, index, deleteHandle: _deleteSelf, customOptions, startIndex, parent, parentPath }) {
const { enableClipboard, src, onEdit, onChange, forceUpdate, displaySize, CustomOperation } = useContext(JsonViewContext);
const currentPath = [...parentPath, String(index)];
const [fold, setFold] = useState(true);
// Edit property
const editHandle = useCallback((indexOrName, newValue, oldValue) => {
originNode[indexOrName] = newValue;
if (onEdit)
onEdit({
newValue,
oldValue,
depth,
src,
indexOrName,
parentType: 'array',
parentPath
});
if (onChange)
onChange({ type: 'edit', depth, src, indexOrName, parentType: 'array', parentPath });
forceUpdate();
}, [node, onEdit, onChange, forceUpdate]);
// Delete property
const deleteHandle = (index) => {
originNode.splice(index, 1);
if (_deleteSelf)
_deleteSelf(index, parentPath);
forceUpdate();
};
const Icons = (jsxs(Fragment, { children: [!fold && (jsxs("span", Object.assign({ onClick: () => setFold(true), className: 'jv-size-chevron' }, { children: [ifDisplay(displaySize, depth, fold) && jsxs("span", Object.assign({ className: 'jv-size' }, { children: [objectSize(node), " Items"] })), jsx(SvgAngleDown, { className: 'jv-chevron' })] }))), !fold && enableClipboard && customCopy(customOptions) && (jsx(CopyButton, { node: node, nodeMeta: { depth, indexOrName: index, parent, parentPath, currentPath } })), typeof CustomOperation === 'function' ? jsx(CustomOperation, { node: node }) : null] }));
return (jsxs("div", { children: [jsx("span", { children: '[' }), Icons, !fold ? (jsx("div", Object.assign({ className: 'jv-indent' }, { children: node.map((n, i) => (jsx(NameValue, { indexOrName: i + startIndex, value: n, depth: depth, parent: node, deleteHandle: deleteHandle, editHandle: editHandle, parentPath: parentPath }, String(index) + String(i)))) }))) : (jsxs("button", Object.assign({ onClick: () => setFold(false), className: 'jv-button' }, { children: [startIndex, " ... ", startIndex + node.length - 1] }))), jsx("span", { children: ']' })] }));
}
function LargeArray({ node, depth, deleteHandle: _deleteSelf, indexOrName, customOptions, parent, parentPath }) {
const currentPath = typeof indexOrName !== 'undefined' ? [...parentPath, String(indexOrName)] : parentPath;
const nestCollapsedArray = [];
for (let i = 0; i < node.length; i += 100) {
nestCollapsedArray.push(node.slice(i, i + 100));
}
const { collapsed, enableClipboard, collapseObjectsAfterLength, editable, onDelete, src, onAdd, CustomOperation, onChange, forceUpdate, displaySize } = useContext(JsonViewContext);
const [fold, setFold] = useState(isCollapsed_largeArray(node, depth, indexOrName, collapsed, collapseObjectsAfterLength, customOptions));
useEffect(() => {
setFold(isCollapsed_largeArray(node, depth, indexOrName, collapsed, collapseObjectsAfterLength, customOptions));
}, [collapsed, collapseObjectsAfterLength]);
// Delete self
const [deleting, setDeleting] = useState(false);
const deleteSelf = () => {
setDeleting(false);
if (_deleteSelf)
_deleteSelf(indexOrName, parentPath);
if (onDelete)
onDelete({ value: node, depth, src, indexOrName: indexOrName, parentType: 'array', parentPath });
if (onChange)
onChange({
type: 'delete',
depth,
src,
indexOrName: indexOrName,
parentType: 'array',
parentPath
});
};
// Add
const [adding, setAdding] = useState(false);
const add = () => {
const arr = node;
arr.push(null);
if (onAdd)
onAdd({ indexOrName: arr.length - 1, depth, src, parentType: 'array', parentPath });
if (onChange)
onChange({ type: 'add', indexOrName: arr.length - 1, depth, src, parentType: 'array', parentPath });
forceUpdate();
};
const isEditing = deleting || adding;
const cancel = () => {
setDeleting(false);
setAdding(false);
};
const Icons = (jsxs(Fragment, { children: [!fold && !isEditing && (jsxs("span", Object.assign({ onClick: () => setFold(true), className: 'jv-size-chevron' }, { children: [ifDisplay(displaySize, depth, fold) && jsxs("span", Object.assign({ className: 'jv-size' }, { children: [node.length, " Items"] })), jsx(SvgAngleDown, { className: 'jv-chevron' })] }))), isEditing && jsx(SvgDone, { className: 'json-view--edit', style: { display: 'inline-block' }, onClick: adding ? add : deleteSelf }), isEditing && jsx(SvgCancel, { className: 'json-view--edit', style: { display: 'inline-block' }, onClick: cancel }), !fold && !isEditing && enableClipboard && customCopy(customOptions) && (jsx(CopyButton, { node: node, nodeMeta: { depth, indexOrName, parent, parentPath, currentPath } })), !fold && !isEditing && editableAdd(editable) && customAdd(customOptions) && (jsx(SvgAddSquare, { className: 'json-view--edit', onClick: () => {
add();
} })), !fold && !isEditing && editableDelete(editable) && customDelete(customOptions) && _deleteSelf && (jsx(SvgTrash, { className: 'json-view--edit', onClick: () => setDeleting(true) })), typeof CustomOperation === 'function' ? jsx(CustomOperation, { node: node }) : null] }));
return (jsxs(Fragment, { children: [jsx("span", { children: '[' }), Icons, !fold ? (jsx("div", Object.assign({ className: 'jv-indent' }, { children: nestCollapsedArray.map((item, index) => (jsx(LargeArrayNode, { originNode: node, node: item, depth: depth, index: index, startIndex: index * 100, deleteHandle: _deleteSelf, customOptions: customOptions, parentPath: parentPath }, String(indexOrName) + String(index)))) }))) : (jsx("button", Object.assign({ onClick: () => setFold(false), className: 'jv-button' }, { children: "..." }))), jsx("span", { children: ']' }), fold && ifDisplay(displaySize, depth, fold) && (jsxs("span", Object.assign({ onClick: () => setFold(false), className: 'jv-size' }, { children: [node.length, " Items"] })))] }));
}
function ObjectNode({ node, depth, indexOrName, deleteHandle: _deleteSelf, customOptions, parent, parentPath }) {
const { collapsed, onCollapse, enableClipboard, ignoreLargeArray, collapseObjectsAfterLength, editable, onDelete, src, onAdd, onEdit, onChange, forceUpdate, displaySize, CustomOperation } = useContext(JsonViewContext);
const currentPath = typeof indexOrName !== 'undefined' ? [...parentPath, String(indexOrName)] : parentPath;
if (!ignoreLargeArray && Array.isArray(node) && node.length > 100) {
return jsx(LargeArray, { node: node, depth: depth, indexOrName: indexOrName, deleteHandle: _deleteSelf, customOptions: customOptions, parentPath: currentPath });
}
const isPlainObject = isObject(node);
const [fold, _setFold] = useState(isCollapsed(node, depth, indexOrName, collapsed, collapseObjectsAfterLength, customOptions));
const setFold = (value) => {
onCollapse === null || onCollapse === void 0 ? void 0 : onCollapse({ isCollapsing: !value, node, depth, indexOrName });
_setFold(value);
};
useEffect(() => {
setFold(isCollapsed(node, depth, indexOrName, collapsed, collapseObjectsAfterLength, customOptions));
}, [collapsed, collapseObjectsAfterLength]);
// Edit property
const editHandle = useCallback((indexOrName, newValue, oldValue) => {
if (Array.isArray(node)) {
node[+indexOrName] = newValue;
}
else if (node) {
node[indexOrName] = newValue;
}
if (onEdit)
onEdit({
newValue,
oldValue,
depth,
src,
indexOrName: indexOrName,
parentType: isPlainObject ? 'object' : 'array',
parentPath: currentPath
});
if (onChange)
onChange({ type: 'edit', depth, src, indexOrName: indexOrName, parentType: isPlainObject ? 'object' : 'array', parentPath: currentPath });
forceUpdate();
}, [node, onEdit, onChange, forceUpdate]);
// Delete property
const deleteHandle = (indexOrName) => {
if (Array.isArray(node)) {
node.splice(+indexOrName, 1);
}
else if (node) {
delete node[indexOrName];
}
forceUpdate();
};
// Delete self
const [deleting, setDeleting] = useState(false);
const deleteSelf = () => {
setDeleting(false);
if (_deleteSelf)
_deleteSelf(indexOrName, currentPath);
if (onDelete)
onDelete({ value: node, depth, src, indexOrName: indexOrName, parentType: isPlainObject ? 'object' : 'array', parentPath: currentPath });
if (onChange)
onChange({
type: 'delete',
depth,
src,
indexOrName: indexOrName,
parentType: isPlainObject ? 'object' : 'array',
parentPath: currentPath
});
};
// Add
const [adding, setAdding] = useState(false);
const inputRef = useRef(null);
const add = () => {
var _a;
if (isPlainObject) {
const inputName = (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.value;
if (inputName) {
node[inputName] = null;
if (inputRef.current)
inputRef.current.value = '';
setAdding(false);
if (onAdd)
onAdd({ indexOrName: inputName, depth, src, parentType: 'object', parentPath: currentPath });
if (onChange)
onChange({ type: 'add', indexOrName: inputName, depth, src, parentType: 'object', parentPath: currentPath });
}
}
else if (Array.isArray(node)) {
const arr = node;
arr.push(null);
if (onAdd)
onAdd({ indexOrName: arr.length - 1, depth, src, parentType: 'array', parentPath: currentPath });
if (onChange)
onChange({ type: 'add', indexOrName: arr.length - 1, depth, src, parentType: 'array', parentPath: currentPath });
}
forceUpdate();
};
const handleAddKeyDown = (event) => {
if (event.key === 'Enter') {
event.preventDefault();
add();
}
else if (event.key === 'Escape') {
cancel();
}
};
const isEditing = deleting || adding;
const cancel = () => {
setDeleting(false);
setAdding(false);
};
const Icons = (jsxs(Fragment, { children: [!fold && !isEditing && (jsxs("span", Object.assign({ onClick: () => setFold(true), className: 'jv-size-chevron' }, { children: [ifDisplay(displaySize, depth, fold) && jsxs("span", Object.assign({ className: 'jv-size' }, { children: [objectSize(node), " Items"] })), jsx(SvgAngleDown, { className: 'jv-chevron' })] }))), adding && isPlainObject && jsx("input", { className: 'json-view--input', placeholder: 'property', ref: inputRef, onKeyDown: handleAddKeyDown }), isEditing && jsx(SvgDone, { className: 'json-view--edit', style: { display: 'inline-block' }, onClick: adding ? add : deleteSelf }), isEditing && jsx(SvgCancel, { className: 'json-view--edit', style: { display: 'inline-block' }, onClick: cancel }), !fold && !isEditing && enableClipboard && customCopy(customOptions) && (jsx(CopyButton, { node: node, nodeMeta: { depth, indexOrName, parent, parentPath, currentPath } })), !fold && !isEditing && editableAdd(editable) && customAdd(customOptions) && (jsx(SvgAddSquare, { className: 'json-view--edit', onClick: () => {
if (isPlainObject) {
setAdding(true);
setTimeout(() => { var _a; return (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus(); });
}
else {
add();
}
} })), !fold && !isEditing && editableDelete(editable) && customDelete(customOptions) && _deleteSelf && (jsx(SvgTrash, { className: 'json-view--edit', onClick: () => setDeleting(true) })), typeof CustomOperation === 'function' ? jsx(CustomOperation, { node: node }) : null] }));
if (Array.isArray(node)) {
return (jsxs(Fragment, { children: [jsx("span", { children: '[' }), Icons, !fold ? (jsx("div", Object.assign({ className: 'jv-indent' }, { children: node.map((n, i) => (jsx(NameValue, { indexOrName: i, value: n, depth: depth, parent: node, deleteHandle: deleteHandle, editHandle: editHandle, parentPath: currentPath }, String(indexOrName) + String(i)))) }))) : (jsx("button", Object.assign({ onClick: () => setFold(false), className: 'jv-button' }, { children: "..." }))), jsx("span", { children: ']' }), fold && ifDisplay(displaySize, depth, fold) && (jsxs("span", Object.assign({ onClick: () => setFold(false), className: 'jv-size' }, { children: [objectSize(node), " Items"] })))] }));
}
else if (isPlainObject) {
return (jsxs(Fragment, { children: [jsx("span", { children: '{' }), Icons, !fold ? (jsx("div", Object.assign({ className: 'jv-indent' }, { children: Object.entries(node).map(([name, value]) => (jsx(NameValue, { indexOrName: name, value: value, depth: depth, parent: node, deleteHandle: deleteHandle, editHandle: editHandle, parentPath: currentPath }, String(indexOrName) + String(name)))) }))) : (jsx("button", Object.assign({ onClick: () => setFold(false), className: 'jv-button' }, { children: "..." }))), jsx("span", { children: '}' }), fold && ifDisplay(displaySize, depth, fold) && (jsxs("span", Object.assign({ onClick: () => setFold(false), className: 'jv-size' }, { children: [objectSize(node), " Items"] })))] }));
}
else {
return jsx("span", { children: String(node) });
}
}
const LongString = React__default.forwardRef(({ str, className, ctrlClick }, ref) => {
let { collapseStringMode, collapseStringsAfterLength, customizeCollapseStringUI } = useContext(JsonViewContext);
const [truncated, setTruncated] = useState(true);
const strRef = useRef(null);
collapseStringsAfterLength = collapseStringsAfterLength > 0 ? collapseStringsAfterLength : 0;
const str_show = str.replace(/\s+/g, ' ');
const collapseStringUI = typeof customizeCollapseStringUI === 'function'
? customizeCollapseStringUI(str_show, truncated)
: typeof customizeCollapseStringUI === 'string'
? customizeCollapseStringUI
: '...';
const clickToTruncateOrEdit = (event) => {
var _a;
if ((event.ctrlKey || event.metaKey) && ctrlClick) {
ctrlClick(event);
}
else {
const selection = window.getSelection();
if (selection && selection.anchorOffset !== selection.focusOffset && ((_a = selection.anchorNode) === null || _a === void 0 ? void 0 : _a.parentElement) === strRef.current)
return;
setTruncated(!truncated);
}
};
if (str.length <= collapseStringsAfterLength)
return (jsxs("span", Object.assign({ ref: strRef, className: className, onClick: ctrlClick }, { children: ["\"", str, "\""] })));
if (collapseStringMode === 'address')
return str.length <= 10 ? (jsxs("span", Object.assign({ ref: strRef, className: className, onClick: ctrlClick }, { children: ["\"", str, "\""] }))) : (jsxs("span", Object.assign({ ref: strRef, onClick: clickToTruncateOrEdit, className: className + ' cursor-pointer' }, { children: ["\"", truncated ? [str_show.slice(0, 6), collapseStringUI, str_show.slice(-4)] : str, "\""] })));
if (collapseStringMode === 'directly') {
return (jsxs("span", Object.assign({ ref: strRef, onClick: clickToTruncateOrEdit, className: className + ' cursor-pointer' }, { children: ["\"", truncated ? [str_show.slice(0, collapseStringsAfterLength), collapseStringUI] : str, "\""] })));
}
if (collapseStringMode === 'word') {
let index_ahead = collapseStringsAfterLength;
let index_behind = collapseStringsAfterLength + 1;
let str_collapsed = str_show;
let count = 1;
while (true) {
if (/\W/.test(str[index_ahead])) {
str_collapsed = str.slice(0, index_ahead);
break;
}
if (/\W/.test(str[index_behind])) {
str_collapsed = str.slice(0, index_behind);
break;
}
if (count === 6) {
str_collapsed = str.slice(0, collapseStringsAfterLength);
break;
}
count++;
index_ahead--;
index_behind++;
}
return (jsxs("span", Object.assign({ ref: strRef, onClick: clickToTruncateOrEdit, className: className + ' cursor-pointer' }, { children: ["\"", truncated ? [str_collapsed, collapseStringUI] : str, "\""] })));
}
return (jsxs("span", Object.assign({ ref: strRef, className: className }, { children: ["\"", str, "\""] })));
});
var _path$1;
function _extends$1() { _extends$1 = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends$1.apply(this, arguments); }
var SvgEdit = function SvgEdit(props) {
return /*#__PURE__*/React.createElement("svg", _extends$1({
xmlns: "http://www.w3.org/2000/svg",
width: 24,
height: 24,
fill: "none",
viewBox: "0 0 24 24"
}, props), _path$1 || (_path$1 = /*#__PURE__*/React.createElement("path", {
fill: "currentColor",
d: "M17.25 3H6.75A3.754 3.754 0 0 0 3 6.75v10.5A3.754 3.754 0 0 0 6.75 21h10.5A3.754 3.754 0 0 0 21 17.25V6.75A3.754 3.754 0 0 0 17.25 3Zm2.25 14.25c0 1.24-1.01 2.25-2.25 2.25H6.75c-1.24 0-2.25-1.01-2.25-2.25V6.75c0-1.24 1.01-2.25 2.25-2.25h10.5c1.24 0 2.25 1.01 2.25 2.25v10.5Zm-6.09-9.466-5.031 5.03a2.981 2.981 0 0 0-.879 2.121v1.19c0 .415.336.75.75.75h1.19c.8 0 1.554-.312 2.12-.879l5.03-5.03a2.252 2.252 0 0 0 0-3.182c-.85-.85-2.331-.85-3.18 0Zm-2.91 7.151c-.28.28-.666.44-1.06.44H9v-.44c0-.4.156-.777.44-1.06l3.187-3.188 1.06 1.061-3.187 3.188Zm5.03-5.03-.782.783-1.06-1.061.782-.782a.766.766 0 0 1 1.06 0 .75.75 0 0 1 0 1.06Z"
})));
};
var _path, _path2;
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
var SvgLink = function SvgLink(props) {
return /*#__PURE__*/React.createElement("svg", _extends({
xmlns: "http://www.w3.org/2000/svg",
width: 24,
height: 24,
fill: "none",
viewBox: "0 0 24 24"
}, props), _path || (_path = /*#__PURE__*/React.createElement("path", {
fill: "currentColor",
d: "M6.75 3h5.5v1.5h-5.5C5.51 4.5 4.5 5.51 4.5 6.75v10.5c0 1.24 1.01 2.25 2.25 2.25h10.5c1.24 0 2.25-1.01 2.25-2.25v-5.5H21v5.5A3.754 3.754 0 0 1 17.25 21H6.75A3.754 3.754 0 0 1 3 17.25V6.75A3.754 3.754 0 0 1 6.75 3Z"
})), _path2 || (_path2 = /*#__PURE__*/React.createElement("path", {
fill: "currentColor",
d: "M20.013 3h-3.946a.987.987 0 0 0 0 1.973h1.564l-6.342 6.342a1.004 1.004 0 0 0 0 1.396 1.004 1.004 0 0 0 1.396 0l6.342-6.342v1.564a.987.987 0 0 0 1.973 0V3.987A.987.987 0 0 0 20.013 3Z"
})));
};
function JsonNode({ node, depth, deleteHandle: _deleteHandle, indexOrName, parent, editHandle, parentPath }) {
// prettier-ignore
const { collapseStringsAfterLength, enableClipboard, editable, src, onDelete, onChange, customizeNode, matchesURL, urlRegExp, EditComponent, DoneComponent, CancelComponent, CustomOperation } = useContext(JsonViewContext);
let customReturn;
if (typeof customizeNode === 'function')
customReturn = safeCall(customizeNode, [{ node, depth, indexOrName }]);
if (customReturn) {
if (isValidElement(customReturn))
return customReturn;
else if (isReactComponent(customReturn)) {
const CustomComponent = customReturn;
return jsx(CustomComponent, { node: node, depth: depth, indexOrName: indexOrName });
}
}
if (Array.isArray(node) || isObject(node)) {
return (jsx(ObjectNode, { parent: parent, node: node, depth: depth, indexOrName: indexOrName, deleteHandle: _deleteHandle, parentPath: parentPath, customOptions: typeof customReturn === 'object' ? customReturn : undefined }));
}
else {
const type = typeof node;
const currentPath = typeof indexOrName !== 'undefined' ? [...parentPath, String(indexOrName)] : parentPath;
const [editing, setEditing] = useState(false);
const [deleting, setDeleting] = useState(false);
const valueRef = useRef(null);
const edit = () => {
setEditing(true);
setTimeout(() => {
var _a, _b;
(_a = window.getSelection()) === null || _a === void 0 ? void 0 : _a.selectAllChildren(valueRef.current);
(_b = valueRef.current) === null || _b === void 0 ? void 0 : _b.focus();
});
};
const done = useCallback(() => {
let newValue = valueRef.current.innerText;
try {
const parsedValue = JSON.parse(newValue);
if (editHandle)
editHandle(indexOrName, parsedValue, node, parentPath);
}
catch (e) {
const trimmedStringValue = resolveEvalFailedNewValue(type, newValue);
if (editHandle)
editHandle(indexOrName, trimmedStringValue, node, parentPath);
}
setEditing(false);
}, [editHandle, indexOrName, node, parentPath, type]);
const cancel = () => {
setEditing(false);
setDeleting(false);
};
const deleteHandle = () => {
setDeleting(false);
if (_deleteHandle)
_deleteHandle(indexOrName, parentPath);
if (onDelete)
onDelete({
value: node,
depth,
src,
indexOrName: indexOrName,
parentType: Array.isArray(parent) ? 'array' : 'object',
parentPath
});
if (onChange)
onChange({
depth,
src,
indexOrName: indexOrName,
parentType: Array.isArray(parent) ? 'array' : 'object',
type: 'delete',
parentPath
});
};
const handleKeyDown = useCallback((event) => {
if (event.key === 'Enter') {
event.preventDefault();
done();
}
else if (event.key === 'Escape') {
cancel();
}
}, [done]);
const isEditing = editing || deleting;
const ctrlClick = !isEditing && editableEdit(editable) && customEdit(customReturn) && editHandle
? (event) => {
if (event.ctrlKey || event.metaKey)
edit();
}
: undefined;
const Icons = (jsxs(Fragment, { children: [isEditing &&
(typeof DoneComponent === 'function' ? (jsx(DoneComponent, { className: 'json-view--edit', style: { display: 'inline-block' }, onClick: deleting ? deleteHandle : done })) : (jsx(SvgDone, { className: 'json-view--edit', style: { display: 'inline-block' }, onClick: deleting ? deleteHandle : done }))), isEditing &&
(typeof CancelComponent === 'function' ? (jsx(CancelComponent, { className: 'json-view--edit', style: { display: 'inline-block' }, onClick: cancel })) : (jsx(SvgCancel, { className: 'json-view--edit', style: { display: 'inline-block' }, onClick: cancel }))), !isEditing && enableClipboard && customCopy(customReturn) && (jsx(CopyButton, { node: node, nodeMeta: { depth, indexOrName, parent, parentPath, currentPath } })), !isEditing && matchesURL && type === 'string' && urlRegExp.test(node) && customMatchesURL(customReturn) && (jsx("a", Object.assign({ href: node, target: '_blank', className: 'json-view--link' }, { children: jsx(SvgLink, {}) }))), !isEditing &&
editableEdit(editable) &&
customEdit(customReturn) &&
editHandle &&
(typeof EditComponent === 'function' ? (jsx(EditComponent, { className: 'json-view--edit', onClick: edit })) : (jsx(SvgEdit, { className: 'json-view--edit', onClick: edit }))), !isEditing && editableDelete(editable) && customDelete(customReturn) && _deleteHandle && (jsx(SvgTrash, { className: 'json-view--edit', onClick: () => setDeleting(true) })), typeof CustomOperation === 'function' ? jsx(CustomOperation, { node: node }) : null] }));
let className = 'json-view--string';
switch (type) {
case 'number':
case 'bigint':
className = 'json-view--number';
break;
case 'boolean':
className = 'json-view--boolean';
break;
case 'object':
className = 'json-view--null';
break;
}
if (typeof (customReturn === null || customReturn === void 0 ? void 0 : customReturn.className) === 'string')
className += ' ' + customReturn.className;
if (deleting)
className += ' json-view--deleting';
let displayValue = String(node);
if (type === 'bigint')
displayValue += 'n';
const EditingElement = useMemo(() => (jsx("span", { contentEditable: true, className: className, dangerouslySetInnerHTML: { __html: type === 'string' ? `"${displayValue}"` : displayValue }, ref: valueRef, onKeyDown: handleKeyDown })), [displayValue, type, handleKeyDown]);
if (type === 'string')
return (jsxs(Fragment, { children: [editing ? (EditingElement) : node.length > collapseStringsAfterLength ? (jsx(LongString, { str: node, ref: valueRef, className: className, ctrlClick: ctrlClick })) : (jsxs("span", Object.assign({ className: className, onClick: ctrlClick }, { children: ["\"", displayValue, "\""] }))), Icons] }));
else {
return (jsxs(Fragment, { children: [editing ? (EditingElement) : (jsx("span", Object.assign({ className: className, onClick: ctrlClick }, { children: displayValue }))), Icons] }));
}
}
}
const defaultURLRegExp = /^(((ht|f)tps?):\/\/)?([^!@#$%^&*?.\s-]([^!@#$%^&*?.\s]{0,63}[^!@#$%^&*?.\s])?\.)+[a-z]{2,6}\/?/;
const JsonViewContext = createContext({
src: undefined,
collapseStringsAfterLength: 99,
collapseStringMode: 'directly',
customizeCollapseStringUI: undefined,
collapseObjectsAfterLength: 20,
collapsed: false,
onCollapse: undefined,
enableClipboard: true,
editable: false,
onEdit: undefined,
onDelete: undefined,
onAdd: undefined,
onChange: undefined,
forceUpdate: () => { },
customizeNode: undefined,
customizeCopy: (() => { }),
displaySize: undefined,
displayArrayIndex: true,
matchesURL: false,
urlRegExp: defaultURLRegExp,
ignoreLargeArray: false,
CopyComponent: undefined,
CopiedComponent: undefined,
EditComponent: undefined,
CancelComponent: undefined,
DoneComponent: undefined,
CustomOperation: undefined
});
function JsonView({ src: _src, collapseStringsAfterLength = 99, collapseStringMode = 'directly', customizeCollapseStringUI, collapseObjectsAfterLength = 99, collapsed, onCollapse, enableClipboard = true, editable = false, onEdit, onDelete, onAdd, onChange, dark = false, theme = 'default', customizeNode, customizeCopy = node => stringifyForCopying(node), displaySize, displayArrayIndex = true, style, className, matchesURL = false, urlRegExp = defaultURLRegExp, ignoreLargeArray = false, CopyComponent, CopiedComponent, EditComponent, CancelComponent, DoneComponent, CustomOperation }) {
const [_, update] = useState(0);
const forceUpdate = useCallback(() => update(state => ++state), []);
const [src, setSrc] = useState(_src);
useEffect(() => setSrc(_src), [_src]);
return (jsx(JsonViewContext.Provider, Object.assign({ value: {
src,
collapseStringsAfterLength,
collapseStringMode,
customizeCollapseStringUI,
collapseObjectsAfterLength,
collapsed,
onCollapse,
enableClipboard,
editable,
onEdit,
onDelete,
onAdd,
onChange,
forceUpdate,
customizeNode,
customizeCopy,
displaySize,
displayArrayIndex,
matchesURL,
urlRegExp,
ignoreLargeArray,
CopyComponent,
CopiedComponent,
EditComponent,
CancelComponent,
DoneComponent,
CustomOperation
} }, { children: jsx("code", Object.assign({ className: 'json-view' + (dark ? ' dark' : '') + (theme && theme !== 'default' ? ' json-view_' + theme : '') + (className ? ' ' + className : ''), style: style }, { children: jsx(JsonNode, { node: src, depth: 1, editHandle: (indexOrName, newValue, oldValue, parentPath) => {
setSrc(newValue);
if (onEdit)
onEdit({
newValue,
oldValue,
depth: 1,
src,
indexOrName: indexOrName,
parentType: null,
parentPath: parentPath
});
if (onChange)
onChange({ type: 'edit', depth: 1, src, indexOrName: indexOrName, parentType: null, parentPath: parentPath });
}, deleteHandle: (indexOrName, parentPath) => {
setSrc(undefined);
if (onDelete)
onDelete({
value: src,
depth: 1,
src,
indexOrName: indexOrName,
parentType: null,
parentPath: parentPath
});
if (onChange)
onChange({
depth: 1,
src,
indexOrName: indexOrName,
parentType: null,
type: 'delete',
parentPath: parentPath
});
}, parentPath: [] }) })) })));
}
export { SvgCancel as CancelSVG, SvgCopied as CopiedSVG, SvgCopy as CopySVG, SvgTrash as DeleteSVG, SvgDone as DoneSVG, SvgEdit as EditSVG, SvgLink as LinkSVG, JsonView as default, defaultURLRegExp, stringifyForCopying as stringify };
//# sourceMappingURL=index.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,11 @@
import JsonView, { defaultURLRegExp, JsonViewProps } from './components/json-view';
import { stringifyForCopying as stringify } from './utils';
import { ReactComponent as EditSVG } from './svgs/edit.svg';
import { ReactComponent as DeleteSVG } from './svgs/trash.svg';
import { ReactComponent as DoneSVG } from './svgs/done.svg';
import { ReactComponent as CancelSVG } from './svgs/cancel.svg';
import { ReactComponent as CopySVG } from './svgs/copy.svg';
import { ReactComponent as CopiedSVG } from './svgs/copied.svg';
import { ReactComponent as LinkSVG } from './svgs/link.svg';
export { JsonView as default, stringify, defaultURLRegExp, EditSVG, DeleteSVG, DoneSVG, CancelSVG, CopySVG, CopiedSVG, LinkSVG };
export type { JsonViewProps };

View File

@@ -0,0 +1,113 @@
export declare const argTypes: {
src: {
description: string;
};
className: {
control: string;
description: string;
};
style: {
control: string;
description: string;
};
dark: {
control: string;
description: string;
table: {
defaultValue: {
summary: boolean;
};
};
};
theme: {
control: string;
options: string[];
table: {
defaultValue: {
summary: boolean;
};
};
};
collapseStringsAfterLength: {
control: string;
description: string;
table: {
defaultValue: {
summary: number;
};
};
};
collapseStringMode: {
control: string;
options: string[];
table: {
defaultValue: {
summary: string;
};
};
};
collapseObjectsAfterLength: {
control: string;
description: string;
table: {
defaultValue: {
summary: number;
};
};
};
collapsed: {
description: string;
table: {
defaultValue: {
summary: boolean;
};
};
};
enableClipboard: {
control: string;
description: string;
table: {
defaultValue: {
summary: boolean;
};
};
};
matchesURL: {
control: string;
description: string;
table: {
defaultValue: {
summary: boolean;
};
};
};
editable: {
table: {
defaultValue: {
summary: boolean;
};
};
description: string;
};
onAdd: {
description: string;
};
onDelete: {
description: string;
};
onEdit: {
description: string;
};
customizeNode: {
description: string;
};
customizeCopy: {
description: string;
};
};
export declare const largeArray: (number | {
1: number;
2: {
2: number;
};
} | (number | (number | number[])[])[])[];

View File

@@ -0,0 +1,35 @@
/// <reference types="react" />
export declare type Collapsed = undefined | number | boolean | ((params: {
node: Record<string, any> | Array<any>;
indexOrName: number | string | undefined;
depth: number;
size: number;
}) => boolean | void);
export type DisplaySize = undefined | number | boolean | 'collapsed' | 'expanded';
export declare type Editable = boolean | {
add?: boolean;
edit?: boolean;
delete?: boolean;
};
export declare type CustomizeOptions = {
add?: boolean;
edit?: boolean;
delete?: boolean;
enableClipboard?: boolean;
matchesURL?: boolean;
collapsed?: boolean;
className?: string;
};
export declare type CustomizeNode = (params: {
node: any;
indexOrName: number | string | undefined;
depth: number;
}) => CustomizeOptions | React.FC | React.Component | React.ReactElement<any, any> | undefined;
export type CustomizeCollapseStringUI = ((str_show: string, truncated: boolean) => JSX.Element | string) | string;
export type NodeMeta = {
depth: number;
indexOrName?: number | string;
parent?: Record<string, any> | Array<any>;
parentPath: string[];
currentPath: string[];
};

View File

@@ -0,0 +1,20 @@
/// <reference types="react" />
import type { Collapsed, CustomizeOptions, DisplaySize, Editable } from './types';
export declare function isObject(node: any): node is Record<string, any>;
export declare function objectSize(node: Record<string, any> | Array<any>): number;
export declare function stringifyForCopying(node: any, space?: string | number | undefined): string;
export declare function writeClipboard(value: string): Promise<void>;
export declare function isCollapsed(node: Record<string, any> | Array<any>, depth: number, indexOrName: number | string | undefined, collapsed: Collapsed, collapseObjectsAfterLength: number, customOptions?: CustomizeOptions): boolean;
export declare function isCollapsed_largeArray(node: Record<string, any> | Array<any>, depth: number, indexOrName: number | string | undefined, collapsed: Collapsed, collapseObjectsAfterLength: number, customOptions?: CustomizeOptions): boolean;
export declare function ifDisplay(displaySize: DisplaySize, depth: number, fold: boolean): boolean;
export declare function safeCall<T extends (...args: any[]) => any>(func: T, params: Parameters<T>): any;
export declare function editableAdd(editable: Editable): true | undefined;
export declare function editableEdit(editable: Editable): true | undefined;
export declare function editableDelete(editable: Editable): true | undefined;
export declare function isReactComponent(component: any): component is (new () => React.Component<any, any>) | React.FC<any>;
export declare function customAdd(customOptions?: CustomizeOptions): boolean;
export declare function customEdit(customOptions?: CustomizeOptions): boolean;
export declare function customDelete(customOptions?: CustomizeOptions): boolean;
export declare function customCopy(customOptions?: CustomizeOptions): boolean;
export declare function customMatchesURL(customOptions?: CustomizeOptions): boolean;
export declare function resolveEvalFailedNewValue(type: string, value: string): string;

View File

@@ -0,0 +1,102 @@
{
"name": "react18-json-view",
"version": "0.2.9",
"type": "module",
"description": "JSON viewer for react18",
"main": "dist/cjs/index.cjs",
"types": "dist/index",
"module": "dist/es/index.mjs",
"brower": "dist/es/index.mjs",
"scripts": {
"dev": "rollup -cw",
"dev:website": "turbo run dev --filter=website...",
"build": "rollup -c",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
"prepare": "husky install",
"clean": "rm -rf dist",
"format": "prettier --write .",
"prebuild": "npm run clean",
"prepublishOnly": "npm run build"
},
"files": [
"src",
"dist"
],
"devDependencies": {
"@rollup/plugin-commonjs": "^24.0.0",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-url": "^8.0.1",
"@storybook/addon-essentials": "^7.3.2",
"@storybook/addon-interactions": "^7.3.2",
"@storybook/addon-links": "^7.3.2",
"@storybook/addon-styling": "^1.3.6",
"@storybook/blocks": "^7.3.2",
"@storybook/react": "^7.3.2",
"@storybook/react-vite": "^7.3.2",
"@svgr/rollup": "^8.1.0",
"@types/node": "^18.11.18",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.8",
"autoprefixer": "^10.4.14",
"husky": "^8.0.3",
"lint-staged": "^13.1.0",
"postcss": "^8.4.23",
"postcss-loader": "^7.2.4",
"prettier": "^2.8.2",
"prettier-plugin-tailwindcss": "^0.2.8",
"react": "^18.2.0",
"rollup": "^3.9.1",
"rollup-plugin-peer-deps-external": "^2.2.4",
"rollup-plugin-typescript2": "^0.34.1",
"storybook": "^7.3.2",
"tailwindcss": "^3.3.2",
"tslib": "^2.7.0",
"turbo": "^1.10.13",
"typescript": "^5.0.4",
"vite-plugin-svgr": "^3.2.0"
},
"peerDependencies": {
"react": ">=16.8.0"
},
"lint-staged": {
"**/*": "prettier --write --ignore-unknown"
},
"license": "MIT",
"author": "Suni",
"keywords": [
"function component",
"interactive",
"interactive-json",
"json",
"json-component",
"json-display",
"json-tree",
"json-view",
"json-viewer",
"json-inspector",
"json-tree",
"react",
"react18",
"react-component",
"react-json",
"theme",
"tree",
"tree-view",
"treeview"
],
"homepage": "https://github.com/YYsuni/react18-json-view#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/YYsuni/react18-json-view.git"
},
"bugs": {
"url": "https://github.com/YYsuni/react18-json-view/issues"
},
"sideEffects": [
"*.css"
],
"dependencies": {
"copy-to-clipboard": "^3.3.3"
}
}

View File

@@ -0,0 +1,42 @@
import { useContext, useState } from 'react'
import { ReactComponent as CopySVG } from '../svgs/copy.svg'
import { ReactComponent as CopiedSVG } from '../svgs/copied.svg'
import { JsonViewContext } from './json-view'
import { writeClipboard } from 'src/utils'
import { NodeMeta } from 'src/types'
interface Props {
node: any
nodeMeta: NodeMeta
}
export default function CopyButton({ node, nodeMeta }: Props) {
const { customizeCopy, CopyComponent, CopiedComponent } = useContext(JsonViewContext)
const [copied, setCopied] = useState(false)
const copyHandler = (event: React.MouseEvent) => {
event.stopPropagation()
const value = customizeCopy(node, nodeMeta)
if (typeof value === 'string' && value) {
writeClipboard(value)
}
setCopied(true)
setTimeout(() => setCopied(false), 3000)
}
return copied ? (
typeof CopiedComponent === 'function' ? (
<CopiedComponent className='json-view--copy' style={{ display: 'inline-block' }} />
) : (
<CopiedSVG className='json-view--copy' style={{ display: 'inline-block' }} />
)
) : typeof CopyComponent === 'function' ? (
<CopyComponent onClick={copyHandler} className='json-view--copy' />
) : (
<CopySVG onClick={copyHandler} className='json-view--copy' />
)
}

View File

@@ -0,0 +1,246 @@
import { useContext, useRef, useState, isValidElement, useMemo, useCallback } from 'react'
import { JsonViewContext } from './json-view'
import {
customCopy,
customDelete,
customEdit,
editableDelete,
editableEdit,
isObject,
isReactComponent,
safeCall,
stringifyForCopying,
resolveEvalFailedNewValue,
customMatchesURL
} from '../utils'
import ObjectNode from './object-node'
import LongString from './long-string'
import CopyButton from './copy-button'
import { ReactComponent as EditSVG } from '../svgs/edit.svg'
import { ReactComponent as DeleteSVG } from '../svgs/trash.svg'
import { ReactComponent as DoneSVG } from '../svgs/done.svg'
import { ReactComponent as CancelSVG } from '../svgs/cancel.svg'
import { ReactComponent as LinkSVG } from '../svgs/link.svg'
import type { CustomizeNode, CustomizeOptions } from '../types'
interface Props {
node: any
depth: number
deleteHandle?: (indexOrName: string | number, parentPath: string[]) => void
editHandle?: (indexOrName: string | number, newValue: any, oldValue: any, parentPath: string[]) => void
indexOrName?: number | string
parent?: Record<string, any> | Array<any>
parentPath: string[]
}
export default function JsonNode({ node, depth, deleteHandle: _deleteHandle, indexOrName, parent, editHandle, parentPath }: Props) {
// prettier-ignore
const { collapseStringsAfterLength, enableClipboard, editable, src, onDelete, onChange, customizeNode, matchesURL, urlRegExp, EditComponent, DoneComponent, CancelComponent, CustomOperation } = useContext(JsonViewContext)
let customReturn: ReturnType<CustomizeNode> | undefined
if (typeof customizeNode === 'function') customReturn = safeCall(customizeNode, [{ node, depth, indexOrName }])
if (customReturn) {
if (isValidElement(customReturn)) return customReturn
else if (isReactComponent(customReturn)) {
const CustomComponent = customReturn
return <CustomComponent node={node} depth={depth} indexOrName={indexOrName} />
}
}
if (Array.isArray(node) || isObject(node)) {
return (
<ObjectNode
parent={parent}
node={node}
depth={depth}
indexOrName={indexOrName}
deleteHandle={_deleteHandle}
parentPath={parentPath}
customOptions={typeof customReturn === 'object' ? (customReturn as CustomizeOptions) : undefined}
/>
)
} else {
const type = typeof node
const currentPath = typeof indexOrName !== 'undefined' ? [...parentPath, String(indexOrName)] : parentPath
const [editing, setEditing] = useState(false)
const [deleting, setDeleting] = useState(false)
const valueRef = useRef<HTMLSpanElement>(null)
const edit = () => {
setEditing(true)
setTimeout(() => {
window.getSelection()?.selectAllChildren(valueRef.current!)
valueRef.current?.focus()
})
}
const done = useCallback(() => {
let newValue = valueRef.current!.innerText
try {
const parsedValue = JSON.parse(newValue)
if (editHandle) editHandle(indexOrName!, parsedValue, node, parentPath)
} catch (e) {
const trimmedStringValue = resolveEvalFailedNewValue(type, newValue)
if (editHandle) editHandle(indexOrName!, trimmedStringValue, node, parentPath)
}
setEditing(false)
}, [editHandle, indexOrName, node, parentPath, type])
const cancel = () => {
setEditing(false)
setDeleting(false)
}
const deleteHandle = () => {
setDeleting(false)
if (_deleteHandle) _deleteHandle(indexOrName!, parentPath)
if (onDelete)
onDelete({
value: node,
depth,
src,
indexOrName: indexOrName!,
parentType: Array.isArray(parent) ? 'array' : 'object',
parentPath
})
if (onChange)
onChange({
depth,
src,
indexOrName: indexOrName!,
parentType: Array.isArray(parent) ? 'array' : 'object',
type: 'delete',
parentPath
})
}
const handleKeyDown = useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === 'Enter') {
event.preventDefault()
done()
} else if (event.key === 'Escape') {
cancel()
}
},
[done]
)
const isEditing = editing || deleting
const ctrlClick =
!isEditing && editableEdit(editable) && customEdit(customReturn as CustomizeOptions | undefined) && editHandle
? (event: React.MouseEvent) => {
if (event.ctrlKey || event.metaKey) edit()
}
: undefined
const Icons = (
<>
{isEditing &&
(typeof DoneComponent === 'function' ? (
<DoneComponent className='json-view--edit' style={{ display: 'inline-block' }} onClick={deleting ? deleteHandle : done} />
) : (
<DoneSVG className='json-view--edit' style={{ display: 'inline-block' }} onClick={deleting ? deleteHandle : done} />
))}
{isEditing &&
(typeof CancelComponent === 'function' ? (
<CancelComponent className='json-view--edit' style={{ display: 'inline-block' }} onClick={cancel} />
) : (
<CancelSVG className='json-view--edit' style={{ display: 'inline-block' }} onClick={cancel} />
))}
{!isEditing && enableClipboard && customCopy(customReturn as CustomizeOptions | undefined) && (
<CopyButton node={node} nodeMeta={{ depth, indexOrName, parent, parentPath, currentPath }} />
)}
{!isEditing && matchesURL && type === 'string' && urlRegExp.test(node) && customMatchesURL(customReturn as CustomizeOptions | undefined) && (
<a href={node} target='_blank' className='json-view--link'>
<LinkSVG />
</a>
)}
{!isEditing &&
editableEdit(editable) &&
customEdit(customReturn as CustomizeOptions | undefined) &&
editHandle &&
(typeof EditComponent === 'function' ? (
<EditComponent className='json-view--edit' onClick={edit} />
) : (
<EditSVG className='json-view--edit' onClick={edit} />
))}
{!isEditing && editableDelete(editable) && customDelete(customReturn as CustomizeOptions | undefined) && _deleteHandle && (
<DeleteSVG className='json-view--edit' onClick={() => setDeleting(true)} />
)}
{typeof CustomOperation === 'function' ? <CustomOperation node={node} /> : null}
</>
)
let className = 'json-view--string'
switch (type) {
case 'number':
case 'bigint':
className = 'json-view--number'
break
case 'boolean':
className = 'json-view--boolean'
break
case 'object':
className = 'json-view--null'
break
}
if (typeof (customReturn as CustomizeOptions)?.className === 'string') className += ' ' + (customReturn as CustomizeOptions).className
if (deleting) className += ' json-view--deleting'
let displayValue = String(node)
if (type === 'bigint') displayValue += 'n'
const EditingElement = useMemo(
() => (
<span
contentEditable
className={className}
dangerouslySetInnerHTML={{ __html: type === 'string' ? `"${displayValue}"` : displayValue }}
ref={valueRef}
onKeyDown={handleKeyDown}
/>
),
[displayValue, type, handleKeyDown]
)
if (type === 'string')
return (
<>
{editing ? (
EditingElement
) : node.length > collapseStringsAfterLength ? (
<LongString str={node} ref={valueRef} className={className} ctrlClick={ctrlClick} />
) : (
<span className={className} onClick={ctrlClick}>
"{displayValue}"
</span>
)}
{Icons}
</>
)
else {
return (
<>
{editing ? (
EditingElement
) : (
<span className={className} onClick={ctrlClick}>
{displayValue}
</span>
)}
{Icons}
</>
)
}
}
}

View File

@@ -0,0 +1,281 @@
import { ReactElement, createContext, useCallback, useEffect, useState } from 'react'
import JsonNode from './json-node'
import type { Collapsed, CustomizeCollapseStringUI, CustomizeNode, DisplaySize, Editable, NodeMeta } from '../types'
import { stringifyForCopying } from '../utils'
type OnEdit = (params: {
newValue: any
oldValue: any
depth: number
src: any
indexOrName: string | number
parentType: 'object' | 'array' | null
parentPath: string[]
}) => void
type OnDelete = (params: {
value: any
indexOrName: string | number
depth: number
src: any
parentType: 'object' | 'array' | null
parentPath: string[]
}) => void
type OnAdd = (params: { indexOrName: string | number; depth: number; src: any; parentType: 'object' | 'array'; parentPath: string[] }) => void
type OnChange = (params: {
indexOrName: string | number
depth: number
src: any
parentType: 'object' | 'array' | null
type: 'add' | 'edit' | 'delete'
parentPath: string[]
}) => void
type OnCollapse = (params: { isCollapsing: boolean; node: Record<string, any> | Array<any>; indexOrName: string | number | undefined; depth: number }) => void
export const defaultURLRegExp = /^(((ht|f)tps?):\/\/)?([^!@#$%^&*?.\s-]([^!@#$%^&*?.\s]{0,63}[^!@#$%^&*?.\s])?\.)+[a-z]{2,6}\/?/
export const JsonViewContext = createContext({
src: undefined as any,
collapseStringsAfterLength: 99,
collapseStringMode: 'directly' as 'directly' | 'word' | 'address',
customizeCollapseStringUI: undefined as CustomizeCollapseStringUI | undefined,
collapseObjectsAfterLength: 20,
collapsed: false as Collapsed,
onCollapse: undefined as OnCollapse | undefined,
enableClipboard: true,
editable: false as Editable,
onEdit: undefined as OnEdit | undefined,
onDelete: undefined as OnDelete | undefined,
onAdd: undefined as OnAdd | undefined,
onChange: undefined as OnChange | undefined,
forceUpdate: () => {},
customizeNode: undefined as CustomizeNode | undefined,
customizeCopy: (() => {}) as (node: any, nodeMeta?: NodeMeta) => any,
displaySize: undefined as DisplaySize,
displayArrayIndex: true,
matchesURL: false,
urlRegExp: defaultURLRegExp,
ignoreLargeArray: false,
CopyComponent: undefined as
| React.FC<{ onClick: (event: React.MouseEvent) => void; className: string }>
| React.Component<{ onClick: (event: React.MouseEvent) => void; className: string }>
| undefined,
CopiedComponent: undefined as
| React.FC<{ className: string; style: React.CSSProperties }>
| React.Component<{ className: string; style: React.CSSProperties }>
| undefined,
EditComponent: undefined as
| React.FC<{ onClick: (event: React.MouseEvent) => void; className: string }>
| React.Component<{ onClick: (event: React.MouseEvent) => void; className: string }>
| undefined,
CancelComponent: undefined as
| React.FC<{ onClick: (event: React.MouseEvent) => void; className: string; style: React.CSSProperties }>
| React.Component<{ onClick: (event: React.MouseEvent) => void; className: string; style: React.CSSProperties }>
| undefined,
DoneComponent: undefined as
| React.FC<{ onClick: (event: React.MouseEvent) => void; className: string; style: React.CSSProperties }>
| React.Component<{ onClick: (event: React.MouseEvent) => void; className: string; style: React.CSSProperties }>
| undefined,
CustomOperation: undefined as React.FC<{ node: any }> | React.Component<{ node: any }> | undefined
})
export interface JsonViewProps {
src: any
collapseStringsAfterLength?: number
collapseStringMode?: 'directly' | 'word' | 'address'
customizeCollapseStringUI?: CustomizeCollapseStringUI
collapseObjectsAfterLength?: number
collapsed?: Collapsed
onCollapse?: OnCollapse
enableClipboard?: boolean
editable?: Editable
onEdit?: OnEdit
onDelete?: OnDelete
onAdd?: OnAdd
onChange?: OnChange
customizeNode?: CustomizeNode
customizeCopy?: (node: any, nodeMeta?: NodeMeta) => any
dark?: boolean
theme?: 'default' | 'a11y' | 'github' | 'vscode' | 'atom' | 'winter-is-coming'
displaySize?: DisplaySize
displayArrayIndex?: boolean
style?: React.CSSProperties
className?: string
matchesURL?: boolean
urlRegExp?: RegExp
ignoreLargeArray?: boolean
CopyComponent?:
| React.FC<{ onClick: (event: React.MouseEvent) => void; className: string }>
| React.Component<{ onClick: (event: React.MouseEvent) => void; className: string }>
CopiedComponent?: React.FC<{ className: string; style: React.CSSProperties }> | React.Component<{ className: string; style: React.CSSProperties }>
EditComponent?:
| React.FC<{ onClick: (event: React.MouseEvent) => void; className: string }>
| React.Component<{ onClick: (event: React.MouseEvent) => void; className: string }>
CancelComponent?:
| React.FC<{ onClick: (event: React.MouseEvent) => void; className: string; style: React.CSSProperties }>
| React.Component<{ onClick: (event: React.MouseEvent) => void; className: string; style: React.CSSProperties }>
DoneComponent?:
| React.FC<{ onClick: (event: React.MouseEvent) => void; className: string; style: React.CSSProperties }>
| React.Component<{ onClick: (event: React.MouseEvent) => void; className: string; style: React.CSSProperties }>
CustomOperation?: React.FC<{ node: any }> | React.Component<{ node: any }>
}
export default function JsonView({
src: _src,
collapseStringsAfterLength = 99,
collapseStringMode = 'directly',
customizeCollapseStringUI,
collapseObjectsAfterLength = 99,
collapsed,
onCollapse,
enableClipboard = true,
editable = false,
onEdit,
onDelete,
onAdd,
onChange,
dark = false,
theme = 'default',
customizeNode,
customizeCopy = node => stringifyForCopying(node),
displaySize,
displayArrayIndex = true,
style,
className,
matchesURL = false,
urlRegExp = defaultURLRegExp,
ignoreLargeArray = false,
CopyComponent,
CopiedComponent,
EditComponent,
CancelComponent,
DoneComponent,
CustomOperation
}: JsonViewProps) {
const [_, update] = useState(0)
const forceUpdate = useCallback(() => update(state => ++state), [])
const [src, setSrc] = useState(_src)
useEffect(() => setSrc(_src), [_src])
return (
<JsonViewContext.Provider
value={{
src,
collapseStringsAfterLength,
collapseStringMode,
customizeCollapseStringUI,
collapseObjectsAfterLength,
collapsed,
onCollapse,
enableClipboard,
editable,
onEdit,
onDelete,
onAdd,
onChange,
forceUpdate,
customizeNode,
customizeCopy,
displaySize,
displayArrayIndex,
matchesURL,
urlRegExp,
ignoreLargeArray,
CopyComponent,
CopiedComponent,
EditComponent,
CancelComponent,
DoneComponent,
CustomOperation
}}>
<code
className={'json-view' + (dark ? ' dark' : '') + (theme && theme !== 'default' ? ' json-view_' + theme : '') + (className ? ' ' + className : '')}
style={style}>
<JsonNode
node={src}
depth={1}
editHandle={(indexOrName: number | string, newValue: any, oldValue: any, parentPath: string[]) => {
setSrc(newValue)
if (onEdit)
onEdit({
newValue,
oldValue,
depth: 1,
src,
indexOrName: indexOrName,
parentType: null,
parentPath: parentPath
})
if (onChange) onChange({ type: 'edit', depth: 1, src, indexOrName: indexOrName, parentType: null, parentPath: parentPath })
}}
deleteHandle={(indexOrName: number | string, parentPath: string[]) => {
setSrc(undefined)
if (onDelete)
onDelete({
value: src,
depth: 1,
src,
indexOrName: indexOrName,
parentType: null,
parentPath: parentPath
})
if (onChange)
onChange({
depth: 1,
src,
indexOrName: indexOrName,
parentType: null,
type: 'delete',
parentPath: parentPath
})
}}
parentPath={[]}
/>
</code>
</JsonViewContext.Provider>
)
}

View File

@@ -0,0 +1,102 @@
import { useCallback, useContext, useState } from 'react'
import { JsonViewContext } from './json-view'
import { customCopy, objectSize, ifDisplay } from '../utils'
import { ReactComponent as AngleDownSVG } from '../svgs/angle-down.svg'
import CopyButton from './copy-button'
import NameValue from './name-value'
import type { CustomizeOptions } from '../types'
interface Props {
originNode: Array<any>
node: Array<any>
depth: number
index: number
deleteHandle?: (_: string | number, currentPath: string[]) => void
customOptions?: CustomizeOptions
startIndex: number
parent?: Record<string, any> | Array<any>
parentPath: string[]
}
export default function LargeArrayNode({ originNode, node, depth, index, deleteHandle: _deleteSelf, customOptions, startIndex, parent, parentPath }: Props) {
const { enableClipboard, src, onEdit, onChange, forceUpdate, displaySize, CustomOperation } = useContext(JsonViewContext)
const currentPath = [...parentPath, String(index)]
const [fold, setFold] = useState(true)
// Edit property
const editHandle = useCallback(
(indexOrName: number | string, newValue: any, oldValue: any) => {
originNode[indexOrName as number] = newValue
if (onEdit)
onEdit({
newValue,
oldValue,
depth,
src,
indexOrName,
parentType: 'array',
parentPath
})
if (onChange) onChange({ type: 'edit', depth, src, indexOrName, parentType: 'array', parentPath })
forceUpdate()
},
[node, onEdit, onChange, forceUpdate]
)
// Delete property
const deleteHandle = (index: number | string) => {
originNode.splice(index as number, 1)
if (_deleteSelf) _deleteSelf(index, parentPath)
forceUpdate()
}
const Icons = (
<>
{!fold && (
<span onClick={() => setFold(true)} className='jv-size-chevron'>
{ifDisplay(displaySize, depth, fold) && <span className='jv-size'>{objectSize(node)} Items</span>}
<AngleDownSVG className='jv-chevron' />
</span>
)}
{!fold && enableClipboard && customCopy(customOptions) && (
<CopyButton node={node} nodeMeta={{ depth, indexOrName: index, parent, parentPath, currentPath }} />
)}
{typeof CustomOperation === 'function' ? <CustomOperation node={node} /> : null}
</>
)
return (
<div>
<span>{'['}</span>
{Icons}
{!fold ? (
<div className='jv-indent'>
{node.map((n, i) => (
<NameValue
key={String(index) + String(i)}
indexOrName={i + startIndex}
value={n}
depth={depth}
parent={node}
deleteHandle={deleteHandle}
editHandle={editHandle}
parentPath={parentPath}
/>
))}
</div>
) : (
<button onClick={() => setFold(false)} className='jv-button'>
{startIndex} ... {startIndex + node.length - 1}
</button>
)}
<span>{']'}</span>
</div>
)
}

View File

@@ -0,0 +1,140 @@
import { useContext, useEffect, useState } from 'react'
import { JsonViewContext } from './json-view'
import { isCollapsed_largeArray, ifDisplay, editableAdd, editableDelete, customAdd, customCopy, customDelete } from '../utils'
import { ReactComponent as AngleDownSVG } from '../svgs/angle-down.svg'
import CopyButton from './copy-button'
import { ReactComponent as DeleteSVG } from '../svgs/trash.svg'
import { ReactComponent as AddSVG } from '../svgs/add-square.svg'
import { ReactComponent as DoneSVG } from '../svgs/done.svg'
import { ReactComponent as CancelSVG } from '../svgs/cancel.svg'
import type { CustomizeOptions } from '../types'
import LargeArrayNode from './large-array-node'
interface Props {
node: Array<any>
depth: number
indexOrName?: number | string
deleteHandle?: (_: string | number, currentPath: string[]) => void
customOptions?: CustomizeOptions
parent?: Record<string, any> | Array<any>
parentPath: string[]
}
export default function LargeArray({ node, depth, deleteHandle: _deleteSelf, indexOrName, customOptions, parent, parentPath }: Props) {
const currentPath = typeof indexOrName !== 'undefined' ? [...parentPath, String(indexOrName)] : parentPath
const nestCollapsedArray: any[] = []
for (let i = 0; i < node.length; i += 100) {
nestCollapsedArray.push(node.slice(i, i + 100))
}
const { collapsed, enableClipboard, collapseObjectsAfterLength, editable, onDelete, src, onAdd, CustomOperation, onChange, forceUpdate, displaySize } =
useContext(JsonViewContext)
const [fold, setFold] = useState(isCollapsed_largeArray(node, depth, indexOrName, collapsed, collapseObjectsAfterLength, customOptions))
useEffect(() => {
setFold(isCollapsed_largeArray(node, depth, indexOrName, collapsed, collapseObjectsAfterLength, customOptions))
}, [collapsed, collapseObjectsAfterLength])
// Delete self
const [deleting, setDeleting] = useState(false)
const deleteSelf = () => {
setDeleting(false)
if (_deleteSelf) _deleteSelf(indexOrName!, parentPath)
if (onDelete) onDelete({ value: node, depth, src, indexOrName: indexOrName!, parentType: 'array', parentPath })
if (onChange)
onChange({
type: 'delete',
depth,
src,
indexOrName: indexOrName!,
parentType: 'array',
parentPath
})
}
// Add
const [adding, setAdding] = useState(false)
const add = () => {
const arr = node as unknown as any[]
arr.push(null)
if (onAdd) onAdd({ indexOrName: arr.length - 1, depth, src, parentType: 'array', parentPath })
if (onChange) onChange({ type: 'add', indexOrName: arr.length - 1, depth, src, parentType: 'array', parentPath })
forceUpdate()
}
const isEditing = deleting || adding
const cancel = () => {
setDeleting(false)
setAdding(false)
}
const Icons = (
<>
{!fold && !isEditing && (
<span onClick={() => setFold(true)} className='jv-size-chevron'>
{ifDisplay(displaySize, depth, fold) && <span className='jv-size'>{node.length} Items</span>}
<AngleDownSVG className='jv-chevron' />
</span>
)}
{isEditing && <DoneSVG className='json-view--edit' style={{ display: 'inline-block' }} onClick={adding ? add : deleteSelf} />}
{isEditing && <CancelSVG className='json-view--edit' style={{ display: 'inline-block' }} onClick={cancel} />}
{!fold && !isEditing && enableClipboard && customCopy(customOptions) && (
<CopyButton node={node} nodeMeta={{ depth, indexOrName, parent, parentPath, currentPath }} />
)}
{!fold && !isEditing && editableAdd(editable) && customAdd(customOptions) && (
<AddSVG
className='json-view--edit'
onClick={() => {
add()
}}
/>
)}
{!fold && !isEditing && editableDelete(editable) && customDelete(customOptions) && _deleteSelf && (
<DeleteSVG className='json-view--edit' onClick={() => setDeleting(true)} />
)}
{typeof CustomOperation === 'function' ? <CustomOperation node={node} /> : null}
</>
)
return (
<>
<span>{'['}</span>
{Icons}
{!fold ? (
<div className='jv-indent'>
{nestCollapsedArray.map((item, index) => (
<LargeArrayNode
key={String(indexOrName) + String(index)}
originNode={node}
node={item}
depth={depth}
index={index}
startIndex={index * 100}
deleteHandle={_deleteSelf}
customOptions={customOptions}
parentPath={parentPath}
/>
))}
</div>
) : (
<button onClick={() => setFold(false)} className='jv-button'>
...
</button>
)}
<span>{']'}</span>
{fold && ifDisplay(displaySize, depth, fold) && (
<span onClick={() => setFold(false)} className='jv-size'>
{node.length} Items
</span>
)}
</>
)
}

View File

@@ -0,0 +1,102 @@
import React, { useContext, useRef, useState } from 'react'
import { JsonViewContext } from './json-view'
interface Props {
str: string
className: string
ctrlClick: ((event: React.MouseEvent) => void) | undefined
}
const LongString = React.forwardRef<HTMLSpanElement, Props>(({ str, className, ctrlClick }, ref) => {
let { collapseStringMode, collapseStringsAfterLength, customizeCollapseStringUI } = useContext(JsonViewContext)
const [truncated, setTruncated] = useState(true)
const strRef = useRef<HTMLSpanElement>(null)
collapseStringsAfterLength = collapseStringsAfterLength > 0 ? collapseStringsAfterLength : 0
const str_show = str.replace(/\s+/g, ' ')
const collapseStringUI =
typeof customizeCollapseStringUI === 'function'
? customizeCollapseStringUI(str_show, truncated)
: typeof customizeCollapseStringUI === 'string'
? customizeCollapseStringUI
: '...'
const clickToTruncateOrEdit = (event: React.MouseEvent) => {
if ((event.ctrlKey || event.metaKey) && ctrlClick) {
ctrlClick(event)
} else {
const selection = window.getSelection()
if (selection && selection.anchorOffset !== selection.focusOffset && selection.anchorNode?.parentElement === strRef.current) return
setTruncated(!truncated)
}
}
if (str.length <= collapseStringsAfterLength)
return (
<span ref={strRef} className={className} onClick={ctrlClick}>
"{str}"
</span>
)
if (collapseStringMode === 'address')
return str.length <= 10 ? (
<span ref={strRef} className={className} onClick={ctrlClick}>
"{str}"
</span>
) : (
<span ref={strRef} onClick={clickToTruncateOrEdit} className={className + ' cursor-pointer'}>
"{truncated ? [str_show.slice(0, 6), collapseStringUI, str_show.slice(-4)] : str}"
</span>
)
if (collapseStringMode === 'directly') {
return (
<span ref={strRef} onClick={clickToTruncateOrEdit} className={className + ' cursor-pointer'}>
"{truncated ? [str_show.slice(0, collapseStringsAfterLength), collapseStringUI] : str}"
</span>
)
}
if (collapseStringMode === 'word') {
let index_ahead = collapseStringsAfterLength
let index_behind = collapseStringsAfterLength + 1
let str_collapsed = str_show
let count = 1
while (true) {
if (/\W/.test(str[index_ahead])) {
str_collapsed = str.slice(0, index_ahead)
break
}
if (/\W/.test(str[index_behind])) {
str_collapsed = str.slice(0, index_behind)
break
}
if (count === 6) {
str_collapsed = str.slice(0, collapseStringsAfterLength)
break
}
count++
index_ahead--
index_behind++
}
return (
<span ref={strRef} onClick={clickToTruncateOrEdit} className={className + ' cursor-pointer'}>
"{truncated ? [str_collapsed, collapseStringUI] : str}"
</span>
)
}
return (
<span ref={strRef} className={className}>
"{str}"
</span>
)
})
export default LongString

View File

@@ -0,0 +1,40 @@
import { useContext } from 'react'
import { JsonViewContext } from './json-view'
import JsonNode from './json-node'
interface Props {
indexOrName: number | string
value: any
depth: number
parent?: Record<string, any> | Array<any>
parentPath: string[]
deleteHandle: (indexOrName: string | number, parentPath: string[]) => void
editHandle: (indexOrName: string | number, newValue: any, oldValue: any, parentPath: string[]) => void
}
export default function NameValue({ indexOrName, value, depth, deleteHandle, editHandle, parent, parentPath }: Props) {
const { displayArrayIndex } = useContext(JsonViewContext)
const isArray = Array.isArray(parent)
return (
<div className='json-view--pair'>
{!isArray || (isArray && displayArrayIndex) ? (
<>
<span className={typeof indexOrName === 'number' ? 'json-view--index' : 'json-view--property'}>{indexOrName}</span>:{' '}
</>
) : (
<></>
)}
<JsonNode
node={value}
depth={depth + 1}
deleteHandle={(indexOrName, parentPath) => deleteHandle(indexOrName, parentPath)}
editHandle={(indexOrName, newValue, oldValue, parentPath) => editHandle(indexOrName, newValue, oldValue, parentPath)}
parent={parent}
indexOrName={indexOrName}
parentPath={parentPath}
/>
</div>
)
}

View File

@@ -0,0 +1,266 @@
import { useCallback, useContext, useEffect, useRef, useState } from 'react'
import { JsonViewContext } from './json-view'
import { isObject, customAdd, customCopy, customDelete, editableAdd, editableDelete, isCollapsed, objectSize, ifDisplay } from '../utils'
import { ReactComponent as AngleDownSVG } from '../svgs/angle-down.svg'
import CopyButton from './copy-button'
import NameValue from './name-value'
import { ReactComponent as DeleteSVG } from '../svgs/trash.svg'
import { ReactComponent as AddSVG } from '../svgs/add-square.svg'
import { ReactComponent as DoneSVG } from '../svgs/done.svg'
import { ReactComponent as CancelSVG } from '../svgs/cancel.svg'
import type { CustomizeOptions } from '../types'
import LargeArray from './large-array'
interface Props {
node: Record<string, any> | Array<any>
depth: number
indexOrName?: number | string
deleteHandle?: (_: string | number, currentPath: string[]) => void
customOptions?: CustomizeOptions
parent?: Record<string, any> | Array<any>
parentPath: string[]
}
export default function ObjectNode({ node, depth, indexOrName, deleteHandle: _deleteSelf, customOptions, parent, parentPath }: Props) {
const {
collapsed,
onCollapse,
enableClipboard,
ignoreLargeArray,
collapseObjectsAfterLength,
editable,
onDelete,
src,
onAdd,
onEdit,
onChange,
forceUpdate,
displaySize,
CustomOperation
} = useContext(JsonViewContext)
const currentPath = typeof indexOrName !== 'undefined' ? [...parentPath, String(indexOrName)] : parentPath
if (!ignoreLargeArray && Array.isArray(node) && node.length > 100) {
return <LargeArray node={node} depth={depth} indexOrName={indexOrName} deleteHandle={_deleteSelf} customOptions={customOptions} parentPath={currentPath} />
}
const isPlainObject = isObject(node)
const [fold, _setFold] = useState(isCollapsed(node, depth, indexOrName, collapsed, collapseObjectsAfterLength, customOptions))
const setFold = (value: boolean) => {
onCollapse?.({ isCollapsing: !value, node, depth, indexOrName })
_setFold(value)
}
useEffect(() => {
setFold(isCollapsed(node, depth, indexOrName, collapsed, collapseObjectsAfterLength, customOptions))
}, [collapsed, collapseObjectsAfterLength])
// Edit property
const editHandle = useCallback(
(indexOrName: number | string, newValue: any, oldValue: any) => {
if (Array.isArray(node)) {
node[+indexOrName] = newValue
} else if (node) {
node[indexOrName] = newValue
}
if (onEdit)
onEdit({
newValue,
oldValue,
depth,
src,
indexOrName: indexOrName,
parentType: isPlainObject ? 'object' : 'array',
parentPath: currentPath
})
if (onChange) onChange({ type: 'edit', depth, src, indexOrName: indexOrName, parentType: isPlainObject ? 'object' : 'array', parentPath: currentPath })
forceUpdate()
},
[node, onEdit, onChange, forceUpdate]
)
// Delete property
const deleteHandle = (indexOrName: number | string) => {
if (Array.isArray(node)) {
node.splice(+indexOrName, 1)
} else if (node) {
delete node[indexOrName]
}
forceUpdate()
}
// Delete self
const [deleting, setDeleting] = useState(false)
const deleteSelf = () => {
setDeleting(false)
if (_deleteSelf) _deleteSelf(indexOrName!, currentPath)
if (onDelete) onDelete({ value: node, depth, src, indexOrName: indexOrName!, parentType: isPlainObject ? 'object' : 'array', parentPath: currentPath })
if (onChange)
onChange({
type: 'delete',
depth,
src,
indexOrName: indexOrName!,
parentType: isPlainObject ? 'object' : 'array',
parentPath: currentPath
})
}
// Add
const [adding, setAdding] = useState(false)
const inputRef = useRef<HTMLInputElement>(null)
const add = () => {
if (isPlainObject) {
const inputName = inputRef.current?.value
if (inputName) {
;(node as Record<string, any>)[inputName] = null
if (inputRef.current) inputRef.current.value = ''
setAdding(false)
if (onAdd) onAdd({ indexOrName: inputName, depth, src, parentType: 'object', parentPath: currentPath })
if (onChange) onChange({ type: 'add', indexOrName: inputName, depth, src, parentType: 'object', parentPath: currentPath })
}
} else if (Array.isArray(node)) {
const arr = node as unknown as any[]
arr.push(null)
if (onAdd) onAdd({ indexOrName: arr.length - 1, depth, src, parentType: 'array', parentPath: currentPath })
if (onChange) onChange({ type: 'add', indexOrName: arr.length - 1, depth, src, parentType: 'array', parentPath: currentPath })
}
forceUpdate()
}
const handleAddKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === 'Enter') {
event.preventDefault()
add()
} else if (event.key === 'Escape') {
cancel()
}
}
const isEditing = deleting || adding
const cancel = () => {
setDeleting(false)
setAdding(false)
}
const Icons = (
<>
{!fold && !isEditing && (
<span onClick={() => setFold(true)} className='jv-size-chevron'>
{ifDisplay(displaySize, depth, fold) && <span className='jv-size'>{objectSize(node)} Items</span>}
<AngleDownSVG className='jv-chevron' />
</span>
)}
{adding && isPlainObject && <input className='json-view--input' placeholder='property' ref={inputRef} onKeyDown={handleAddKeyDown} />}
{isEditing && <DoneSVG className='json-view--edit' style={{ display: 'inline-block' }} onClick={adding ? add : deleteSelf} />}
{isEditing && <CancelSVG className='json-view--edit' style={{ display: 'inline-block' }} onClick={cancel} />}
{!fold && !isEditing && enableClipboard && customCopy(customOptions) && (
<CopyButton node={node} nodeMeta={{ depth, indexOrName, parent, parentPath, currentPath }} />
)}
{!fold && !isEditing && editableAdd(editable) && customAdd(customOptions) && (
<AddSVG
className='json-view--edit'
onClick={() => {
if (isPlainObject) {
setAdding(true)
setTimeout(() => inputRef.current?.focus())
} else {
add()
}
}}
/>
)}
{!fold && !isEditing && editableDelete(editable) && customDelete(customOptions) && _deleteSelf && (
<DeleteSVG className='json-view--edit' onClick={() => setDeleting(true)} />
)}
{typeof CustomOperation === 'function' ? <CustomOperation node={node} /> : null}
</>
)
if (Array.isArray(node)) {
return (
<>
<span>{'['}</span>
{Icons}
{!fold ? (
<div className='jv-indent'>
{node.map((n, i) => (
<NameValue
key={String(indexOrName) + String(i)}
indexOrName={i}
value={n}
depth={depth}
parent={node}
deleteHandle={deleteHandle}
editHandle={editHandle}
parentPath={currentPath}
/>
))}
</div>
) : (
<button onClick={() => setFold(false)} className='jv-button'>
...
</button>
)}
<span>{']'}</span>
{fold && ifDisplay(displaySize, depth, fold) && (
<span onClick={() => setFold(false)} className='jv-size'>
{objectSize(node)} Items
</span>
)}
</>
)
} else if (isPlainObject) {
return (
<>
<span>{'{'}</span>
{Icons}
{!fold ? (
<div className='jv-indent'>
{Object.entries(node).map(([name, value]) => (
<NameValue
key={String(indexOrName) + String(name)}
indexOrName={name}
value={value}
depth={depth}
parent={node}
deleteHandle={deleteHandle}
editHandle={editHandle}
parentPath={currentPath}
/>
))}
</div>
) : (
<button onClick={() => setFold(false)} className='jv-button'>
...
</button>
)}
<span>{'}'}</span>
{fold && ifDisplay(displaySize, depth, fold) && (
<span onClick={() => setFold(false)} className='jv-size'>
{objectSize(node)} Items
</span>
)}
</>
)
} else {
return <span>{String(node)}</span>
}
}

View File

@@ -0,0 +1,54 @@
:is(.dark .json-view, .dark.json-view) {
color: #d1d1d1;
--json-property: #009033;
--json-index: #5d75f2;
--json-number: #5d75f2;
--json-string: #c57e29;
--json-boolean: #e4407b;
--json-null: #e4407b;
}
:is(.dark .json-view_a11y, .dark.json-view_a11y) {
color: #d1d1d1;
--json-property: #ffd700;
--json-index: #00e0e0;
--json-number: #00e0e0;
--json-string: #abe338;
--json-boolean: #ffa07a;
--json-null: #ffa07a;
}
:is(.dark .json-view_github, .dark.json-view_github) {
color: #79b8ff;
--json-property: #79b8ff;
--json-index: #79b8ff;
--json-number: #79b8ff;
--json-string: #9ecbff;
--json-boolean: #79b8ff;
--json-null: #79b8ff;
}
:is(.dark .json-view_vscode, .dark.json-view_vscode) {
color: #da70d6;
--json-property: #9cdcfe;
--json-index: #b5cea8;
--json-number: #b5cea8;
--json-string: #ce9178;
--json-boolean: #569cd6;
--json-null: #569cd6;
}
:is(.dark .json-view_atom, .dark.json-view_atom) {
color: #abb2bf;
--json-property: #e06c75;
--json-index: #d19a66;
--json-number: #d19a66;
--json-string: #98c379;
--json-boolean: #56b6c2;
--json-null: #56b6c2;
}
:is(.dark .json-view_winter-is-coming, .dark.json-view_winter-is-coming) {
color: #a7dbf7;
--json-property: #91dacd;
--json-index: #8dec95;
--json-number: #8dec95;
--json-string: #e0aff5;
--json-boolean: #f29fd8;
--json-null: #f29fd8;
}

View File

@@ -0,0 +1,5 @@
declare module '*.svg' {
const src: string
export default src
export const ReactComponent: React.FC<React.SVGProps<SVGSVGElement>>
}

View File

@@ -0,0 +1,13 @@
import JsonView, { defaultURLRegExp, JsonViewProps } from './components/json-view'
import { stringifyForCopying as stringify } from './utils'
import { ReactComponent as EditSVG } from './svgs/edit.svg'
import { ReactComponent as DeleteSVG } from './svgs/trash.svg'
import { ReactComponent as DoneSVG } from './svgs/done.svg'
import { ReactComponent as CancelSVG } from './svgs/cancel.svg'
import { ReactComponent as CopySVG } from './svgs/copy.svg'
import { ReactComponent as CopiedSVG } from './svgs/copied.svg'
import { ReactComponent as LinkSVG } from './svgs/link.svg'
export { JsonView as default, stringify, defaultURLRegExp, EditSVG, DeleteSVG, DoneSVG, CancelSVG, CopySVG, CopiedSVG, LinkSVG }
export type { JsonViewProps }

View File

@@ -0,0 +1,337 @@
import React from 'react'
import { Meta, StoryObj } from '@storybook/react'
import JsonView from '../index'
import { argTypes, largeArray } from './share'
import { stringifyForCopying } from '../utils'
type TYPE_FC = typeof JsonView
export default {
title: 'Editable',
component: JsonView,
argTypes,
args: {
enableClipboard: true,
editable: true,
src: {
string: 'string',
longString: 'long string long string long string long string long string long string',
number: 123456,
boolean: false,
null: null,
func: function () {
console.log('Hello World')
},
Symbol: Symbol('JSON View'),
obj: {
k1: 123,
k2: '123',
k3: false,
nested: {
k4: 'nested'
}
},
arr: ['string', 123456, false, null]
},
onEdit: ({ newValue, src, oldValue, indexOrName, parentPath }) => {
console.log('[onEdit]', indexOrName, newValue, oldValue, src, parentPath)
},
onDelete: ({ value, src, indexOrName, parentPath }) => {
console.log('[onDelete]', indexOrName, value, src, parentPath)
},
onAdd: ({ src, indexOrName, parentPath }) => {
console.log('[onAdd]', indexOrName, src, parentPath)
},
onChange: ({ src, indexOrName, parentPath }) => {
console.log('[onChange]', indexOrName, src, parentPath)
}
},
decorators: [
Story => (
<div
className='flex h-full items-center justify-center overflow-auto p-8'
style={{ backgroundImage: 'linear-gradient(140deg, rgb(165, 142, 251), rgb(233, 191, 248))' }}>
<div className='max-w-[600px] shrink-0 rounded-xl bg-white/90 p-6 font-mono shadow backdrop-blur'>
<Story />
</div>
</div>
)
]
} as Meta<TYPE_FC>
export const Primary: StoryObj<TYPE_FC> = {}
export const DisplaySize: StoryObj<TYPE_FC> = {
args: {
src: {
string: 'string',
longString: 'long string long string long string long string long string long string',
number: 123456,
boolean: false,
null: null,
func: function () {},
Symbol: Symbol('JSON View'),
arr: ['string', 123456, false, null],
nest: {
nest: {
nest: {
nest: {
over: 'over'
}
}
}
}
},
displaySize: 'collapsed'
}
}
export const XS: StoryObj<TYPE_FC> = {
decorators: [
Story => (
<div className='text-xs'>
<Story />
</div>
)
]
}
export const LG: StoryObj<TYPE_FC> = {
decorators: [
Story => (
<div className='text-lg'>
<Story />
</div>
)
]
}
export const XL: StoryObj<TYPE_FC> = {
decorators: [
Story => (
<div className='text-xl'>
<Story />
</div>
)
]
}
export const String: StoryObj<TYPE_FC> = {
args: {
src: 'string'
}
}
export const LongString: StoryObj<TYPE_FC> = {
args: {
src: 'long string long string long string long string'
}
}
export const Number: StoryObj<TYPE_FC> = {
args: {
src: 12312312321
}
}
export const Null: StoryObj<TYPE_FC> = {
args: {
src: null
}
}
export const Boolean: StoryObj<TYPE_FC> = {
args: {
src: true
}
}
export const Undefined: StoryObj<TYPE_FC> = {
args: {
src: undefined
}
}
export const Collapsed_Boolean: StoryObj<TYPE_FC> = {
args: {
src: {
string: 'string',
longString: 'long string long string long string long string long string long string',
number: 123456,
boolean: false,
null: null,
func: function () {},
Symbol: Symbol('JSON View'),
obj: {
k1: 123,
k2: '123',
k3: false
},
arr: ['string', 123456, false, null]
},
collapsed: true
},
decorators: [
Story => (
<div style={{ minWidth: 300 }}>
<Story />
</div>
)
]
}
export const Collapsed_Number: StoryObj<TYPE_FC> = {
args: {
src: {
boolean: false,
null: null,
obj: {
k1: 123,
k2: '123',
k3: false,
k4: {
k: {
k: {
k: 'k5'
}
}
}
},
arr: ['string', 123456, false, null, [123, 123, 123, [123, 123, 123]]]
},
collapsed: 2
},
decorators: [
Story => (
<div style={{ minWidth: 300 }}>
<Story />
</div>
)
]
}
export const Editable_Options: StoryObj<TYPE_FC> = {
args: {
src: {
boolean: false,
null: null,
obj: {
k1: 123,
k2: '123',
k3: false,
k4: {
k: {
k: {
k: 'k5'
}
}
}
},
arr: ['string', 123456, false, null, [123, 123, 123, [123, 123, 123]]]
},
editable: {
add: false,
edit: true,
delete: false
}
},
decorators: [
Story => (
<div style={{ minWidth: 300 }}>
<Story />
</div>
)
]
}
export const MatchesURL: StoryObj<TYPE_FC> = {
args: {
src: {
string: 'string',
link: 'https://www.google.com/',
number: 123456,
boolean: false,
null: null,
func: function () {},
Symbol: Symbol('JSON View'),
obj: {
k1: 123,
k2: '123',
k3: false
},
arr: ['string', 123456, false, null]
},
matchesURL: true,
editable: true
}
}
export const CustomizeCopy: StoryObj<TYPE_FC> = {
args: {
src: {
string: 'string',
link: 'https://www.google.com/',
number: 123456,
boolean: false,
null: null,
func: function () {},
Symbol: Symbol('JSON View'),
obj: {
k1: 123,
k2: '123',
k3: false
},
arr: ['string', 123456, false, null]
},
editable: true,
customizeCopy: (node: any) => stringifyForCopying(node, 4)
}
}
export const LargeArray: StoryObj<TYPE_FC> = {
args: {
src: {
obj: {
k1: 123,
k2: '123',
k3: false
},
largeArray: largeArray
},
editable: true,
displaySize: true,
collapsed: 2
}
}
export const CustomIcons: StoryObj<TYPE_FC> = {
args: {
src: {
obj: {
k1: 123,
k2: '123',
k3: false
}
},
editable: true,
displaySize: true,
CopyComponent: ({ onClick, className }) => (
<svg onClick={onClick} className={className} height='512' viewBox='0 0 24 24' width='512' xmlns='http://www.w3.org/2000/svg' data-name='Layer 1'>
<path d='m15 20h-10a5.006 5.006 0 0 1 -5-5v-10a5.006 5.006 0 0 1 5-5h10a5.006 5.006 0 0 1 5 5v10a5.006 5.006 0 0 1 -5 5zm9-1v-13a1 1 0 0 0 -2 0v13a3 3 0 0 1 -3 3h-13a1 1 0 0 0 0 2h13a5.006 5.006 0 0 0 5-5z' />
</svg>
),
CopiedComponent: ({ className, style }) => (
<svg className={className} style={style} xmlns='http://www.w3.org/2000/svg' version='1.1' x='0px' y='0px' viewBox='0 0 512 512' width='512' height='512'>
<path d='M405.333,0H106.667C47.786,0.071,0.071,47.786,0,106.667v298.667C0.071,464.214,47.786,511.93,106.667,512h298.667 C464.214,511.93,511.93,464.214,512,405.333V106.667C511.93,47.786,464.214,0.071,405.333,0z M426.667,172.352L229.248,369.771 c-16.659,16.666-43.674,16.671-60.34,0.012c-0.004-0.004-0.008-0.008-0.012-0.012l-83.563-83.541 c-8.348-8.348-8.348-21.882,0-30.229s21.882-8.348,30.229,0l83.541,83.541l197.44-197.419c8.348-8.318,21.858-8.294,30.176,0.053 C435.038,150.524,435.014,164.034,426.667,172.352z' />
</svg>
),
EditComponent: ({ onClick, className }) => (
<svg onClick={onClick} className={className} fill="none" height="24" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
),
CancelComponent: ({ onClick, className }) => (
<svg onClick={onClick} className={className} enable-background="new 0 0 32 32" height="32px" id="svg2" version="1.1" viewBox="0 0 32 32" width="32px" xmlns="http://www.w3.org/2000/svg"><g id="background"><rect fill="none" height="32" width="32"/></g><g id="cancel"><polygon points="2,26 6,30 16,20 26,30 30,26 20,16 30,6 26,2 16,12 6,2 2,6 12,16 "/></g></svg>
),
DoneComponent: ({ onClick, className }) => (
<svg onClick={onClick} className={className} height="48" viewBox="0 0 48 48" width="48" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h48v48H0z" fill="none"/><path d="M36 14l-2.83-2.83-12.68 12.69 2.83 2.83L36 14zm8.49-2.83L23.31 32.34 14.97 24l-2.83 2.83L23.31 38l24-24-2.82-2.83zM.83 26.83L12 38l2.83-2.83L3.66 24 .83 26.83z"/></svg>
),
}
}

View File

@@ -0,0 +1,511 @@
import React from 'react'
import { Meta, StoryObj } from '@storybook/react'
import JsonView from '../index'
import { argTypes, largeArray } from './share'
import { stringifyForCopying } from '../utils'
type TYPE_FC = typeof JsonView
export default {
title: 'JSON View',
component: JsonView,
argTypes,
decorators: [
Story => (
<div
className='flex h-full items-center justify-center overflow-auto p-8'
style={{ backgroundImage: 'linear-gradient(140deg, rgb(165, 142, 251), rgb(233, 191, 248))' }}>
<div className='max-w-[600px] rounded-xl bg-white/90 p-6 font-mono shadow backdrop-blur'>
<Story />
</div>
</div>
)
]
} as Meta<TYPE_FC>
export const Primary: StoryObj<TYPE_FC> = {
args: {
src: {
string: 'string',
longString: 'long string long string long string long string long string long string',
number: 123456,
boolean: false,
null: null,
func: function () {},
Symbol: Symbol('JSON View'),
obj: {
k1: 123,
k2: '123',
k3: false
},
arr: ['string', 123456, false, null]
}
}
}
export const DisplaySize: StoryObj<TYPE_FC> = {
args: {
src: {
string: 'string',
longString: 'long string long string long string long string long string long string',
number: 123456,
boolean: false,
null: null,
func: function () {},
Symbol: Symbol('JSON View'),
arr: ['string', 123456, false, null]
},
displaySize: true
}
}
export const SM: StoryObj<TYPE_FC> = {
args: {
src: {
string: 'string',
longString: 'long string long string long string long string long string long string',
number: 123456,
boolean: false,
null: null,
func: function () {},
Symbol: Symbol('JSON View'),
arr: ['string', 123456, false, null]
},
enableClipboard: true,
displaySize: true
},
decorators: [
Story => (
<div className='text-sm'>
<Story />
</div>
)
]
}
export const XS: StoryObj<TYPE_FC> = {
args: {
src: {
string: 'string',
longString: 'long string long string long string long string long string long string',
number: 123456,
boolean: false,
null: null,
func: function () {},
Symbol: Symbol('JSON View'),
arr: ['string', 123456, false, null]
},
enableClipboard: true,
displaySize: true
},
decorators: [
Story => (
<div className='text-xs'>
<Story />
</div>
)
]
}
export const LG: StoryObj<TYPE_FC> = {
args: {
src: {
string: 'string',
longString: 'long string long string long string long string long string long string',
number: 123456,
boolean: false,
null: null,
func: function () {},
Symbol: Symbol('JSON View'),
arr: ['string', 123456, false, null]
},
enableClipboard: true,
displaySize: true
},
decorators: [
Story => (
<div className='text-lg'>
<Story />
</div>
)
]
}
export const XL: StoryObj<TYPE_FC> = {
args: {
src: {
string: 'string',
longString: 'long string long string long string long string long string long string',
number: 123456,
boolean: false,
null: null,
func: function () {},
Symbol: Symbol('JSON View'),
arr: ['string', 123456, false, null]
},
enableClipboard: true,
displaySize: true
},
decorators: [
Story => (
<div className='text-xl'>
<Story />
</div>
)
]
}
export const BigObject: StoryObj<TYPE_FC> = {
args: {
src: {
string: 'string',
longString: 'long string long string long string long string',
number: 123456,
boolean: false,
null: null,
Date: new Date(),
Symbol: Symbol('JSON View'),
arr: ['string', 123456, false, null],
bigObject: {
abc0: 'abc',
abc1: 'abc',
abc2: 'abc',
abc3: 'abc',
abc4: 'abc',
abc5: 'abc',
abc6: 'abc',
abc7: 'abc',
abc8: 'abc',
abc9: 'abc',
abc10: 'abc',
abc11: 'abc',
abc12: 'abc',
abc13: 'abc',
abc14: 'abc',
abc15: 'abc',
abc16: 'abc',
abc17: 'abc',
abc18: 'abc',
abc19: 'abc',
abc20: 'abc',
abc21: 'abc',
abc22: 'abc',
abc23: 'abc',
abc24: 'abc',
abc25: 'abc',
abc26: 'abc',
abc27: 'abc',
abc28: 'abc',
abc29: 'abc'
}
},
collapseObjectsAfterLength: 20
}
}
export const Array: StoryObj<TYPE_FC> = {
args: {
src: ['string', 123456, false, null, { string: 'string', number: 123456, boolean: false, null: null, Date: new Date(), Symbol: Symbol('JSON View') }],
collapsed: 1
}
}
export const BigArray: StoryObj<TYPE_FC> = {
args: {
src: [
'string',
123456,
false,
null,
{ string: 'string', number: 123456, boolean: false, null: null, Date: new Date(), Symbol: Symbol('JSON View') },
[
'string',
123456,
false,
'string',
123456,
false,
'string',
123456,
false,
'string',
123456,
false,
'string',
123456,
false,
'string',
123456,
false,
'string',
123456,
false,
'string',
123456,
false
]
],
collapseObjectsAfterLength: 20
}
}
export const String: StoryObj<TYPE_FC> = {
args: {
src: 'string'
}
}
export const LongString: StoryObj<TYPE_FC> = {
args: {
src: 'long string long string long string long string',
collapseStringsAfterLength: 20,
collapseStringMode: 'word'
}
}
export const CustomizeCollapseStringUI: StoryObj<TYPE_FC> = {
args: {
src: 'long string long string long string long string',
collapseStringsAfterLength: 20,
collapseStringMode: 'word',
customizeCollapseStringUI: () => (
<>
... <button style={{ margin: '0 10px', padding: '5px', borderRadius: '5px', fontSize: '12px', color: 'black', background: '#dcdcdc' }}>expand</button>
</>
)
}
}
export const Number: StoryObj<TYPE_FC> = {
args: {
src: 12312312321
}
}
export const Null: StoryObj<TYPE_FC> = {
args: {
src: null
}
}
export const Boolean: StoryObj<TYPE_FC> = {
args: {
src: true
}
}
export const Undefined: StoryObj<TYPE_FC> = {
args: {
src: undefined
}
}
export const Collapsed_Boolean: StoryObj<TYPE_FC> = {
args: {
src: {
string: 'string',
longString: 'long string long string long string long string long string long string',
number: 123456,
boolean: false,
null: null,
func: function () {},
Symbol: Symbol('JSON View'),
obj: {
k1: 123,
k2: '123',
k3: false
},
arr: ['string', 123456, false, null]
},
collapsed: true
},
decorators: [
Story => (
<div style={{ minWidth: 300 }}>
<Story />
</div>
)
]
}
export const Collapsed_Number: StoryObj<TYPE_FC> = {
args: {
src: {
boolean: false,
null: null,
obj: {
k1: 123,
k2: '123',
k3: false,
k4: {
k: {
k: {
k: 'k5'
}
}
}
},
arr: ['string', 123456, false, null, [123, 123, 123, [123, 123, 123]]]
},
collapsed: 2
},
decorators: [
Story => (
<div style={{ minWidth: 300 }}>
<Story />
</div>
)
]
}
export const Collapsed_Function: StoryObj<TYPE_FC> = {
args: {
src: {
boolean: false,
null: null,
obj: {
k1: 123,
k2: '123',
k3: false,
k4: {
k: {
k: {
k: 'k5'
}
}
}
},
arr: ['string', 123456, false, null, [123, 123, 123, [123, 123, 123]]],
arr2: [1, 2]
},
collapsed: params => {
if (params.depth > 3) return true
if (params.depth > 2 && params.size > 4) return true
return false
}
},
decorators: [
Story => (
<div style={{ minWidth: 300 }}>
<Story />
</div>
)
]
}
export const CustomizeNode: StoryObj<TYPE_FC> = {
args: {
editable: true,
src: {
suni: 'suni',
string: 'string',
longString: 'long string long string long string long string long string long string',
number: 123456,
boolean: false,
null: null,
func: function () {},
Symbol: Symbol('JSON View'),
obj: {
k1: 123,
k2: '123',
k3: false
},
arr: ['string', 123456, false, null, [1, 2, 3]]
},
customizeNode: params => {
if (params.node === 'suni') return () => <span className='underline'>suni</span>
if (params.node === 123) return <b>123</b>
if (params.indexOrName === 'obj') return { add: false, delete: false, enableClipboard: false }
if (params.node === 'string') return { edit: true, enableClipboard: false, delete: false }
if (params.indexOrName === 'arr') return { collapsed: false }
if (params.depth > 2) return { collapsed: true }
if (params.indexOrName === 'func') return { className: 'underline' }
}
},
decorators: [
Story => (
<div style={{ minWidth: 300 }}>
<Story />
</div>
)
]
}
export const MatchesURL: StoryObj<TYPE_FC> = {
args: {
src: {
string: 'string',
link: 'https://www.google.com/',
number: 123456,
boolean: false,
null: null,
func: function () {},
Symbol: Symbol('JSON View'),
obj: {
k1: 123,
k2: '123',
k3: false
},
arr: ['string', 123456, false, null]
},
matchesURL: true
}
}
export const CustomizeCopy: StoryObj<TYPE_FC> = {
args: {
src: {
string: 'string',
link: 'https://www.google.com/',
number: 123456,
boolean: false,
null: null,
func: function () {},
Symbol: Symbol('JSON View'),
obj: {
k1: 123,
k2: '123',
k3: false
},
arr: ['string', 123456, false, null]
},
customizeCopy: (node: any) => stringifyForCopying(node, 4)
}
}
export const LargeArray: StoryObj<TYPE_FC> = {
args: {
src: {
obj: {
k1: 123,
k2: '123',
k3: false
},
largeArray: largeArray
},
displaySize: true
}
}
export const CustomIcons: StoryObj<TYPE_FC> = {
args: {
src: {
obj: {
k1: 123,
k2: '123',
k3: false
}
},
displaySize: true,
CopyComponent: ({ onClick, className }) => (
<svg onClick={onClick} className={className} height='512' viewBox='0 0 24 24' width='512' xmlns='http://www.w3.org/2000/svg' data-name='Layer 1'>
<path d='m15 20h-10a5.006 5.006 0 0 1 -5-5v-10a5.006 5.006 0 0 1 5-5h10a5.006 5.006 0 0 1 5 5v10a5.006 5.006 0 0 1 -5 5zm9-1v-13a1 1 0 0 0 -2 0v13a3 3 0 0 1 -3 3h-13a1 1 0 0 0 0 2h13a5.006 5.006 0 0 0 5-5z' />
</svg>
),
CopiedComponent: ({ className, style }) => (
<svg className={className} style={style} xmlns='http://www.w3.org/2000/svg' version='1.1' x='0px' y='0px' viewBox='0 0 512 512' width='512' height='512'>
<path d='M405.333,0H106.667C47.786,0.071,0.071,47.786,0,106.667v298.667C0.071,464.214,47.786,511.93,106.667,512h298.667 C464.214,511.93,511.93,464.214,512,405.333V106.667C511.93,47.786,464.214,0.071,405.333,0z M426.667,172.352L229.248,369.771 c-16.659,16.666-43.674,16.671-60.34,0.012c-0.004-0.004-0.008-0.008-0.012-0.012l-83.563-83.541 c-8.348-8.348-8.348-21.882,0-30.229s21.882-8.348,30.229,0l83.541,83.541l197.44-197.419c8.348-8.318,21.858-8.294,30.176,0.053 C435.038,150.524,435.014,164.034,426.667,172.352z' />
</svg>
)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,215 @@
import React from 'react'
import { Meta, StoryObj } from '@storybook/react'
import JsonView from '../index'
import '../dark.css'
import { argTypes } from './share'
type TYPE_FC = typeof JsonView
export default {
title: 'Themes',
component: JsonView,
argTypes,
args: {
enableClipboard: true,
editable: true,
src: {
string: 'string',
longString: 'long string long string long string long string long string long string',
number: 123456,
boolean: false,
null: null,
func: function () {
console.log('Hello World')
},
Symbol: Symbol('JSON View'),
obj: {
k1: 123,
k2: '123',
k3: false
},
arr: ['string', 123456, false, null]
}
}
} as Meta<TYPE_FC>
export const Default: StoryObj<TYPE_FC> = {
args: {
theme: 'default'
},
decorators: [
Story => (
<div className='flex h-full items-center justify-center overflow-auto bg-[#eee] p-8'>
<div className='max-w-[600px] rounded-xl bg-white p-6 font-mono shadow backdrop-blur'>
<Story />
</div>
</div>
)
]
}
export const Default_Dark: StoryObj<TYPE_FC> = {
args: {
theme: 'default',
dark: true
},
decorators: [
Story => (
<div className='flex h-full items-center justify-center overflow-auto bg-[#334] p-8'>
<div className='max-w-[600px] rounded-xl bg-[#0E0832] p-6 font-mono shadow backdrop-blur'>
<Story />
</div>
</div>
)
]
}
export const Accessibility: StoryObj<TYPE_FC> = {
args: {
theme: 'a11y'
},
decorators: [
Story => (
<div className='flex h-full items-center justify-center overflow-auto bg-[#eee] p-8'>
<div className='max-w-[600px] rounded-xl bg-[#fefefe] p-6 font-mono shadow backdrop-blur'>
<Story />
</div>
</div>
)
]
}
export const Accessibility_Dark: StoryObj<TYPE_FC> = {
args: {
theme: 'a11y',
dark: true
},
decorators: [
Story => (
<div className='flex h-full items-center justify-center overflow-auto bg-[#333] p-8 '>
<div className='max-w-[600px] rounded-xl bg-[#2B2B2B] p-6 font-mono shadow backdrop-blur'>
<Story />
</div>
</div>
)
]
}
export const Github: StoryObj<TYPE_FC> = {
args: {
theme: 'github'
},
decorators: [
Story => (
<div className='flex h-full items-center justify-center overflow-auto bg-[#eee] p-8'>
<div className='max-w-[600px] rounded-xl bg-[#fff] p-6 font-mono shadow backdrop-blur'>
<Story />
</div>
</div>
)
]
}
export const Github_Dark: StoryObj<TYPE_FC> = {
args: {
theme: 'github',
dark: true
},
decorators: [
Story => (
<div className='flex h-full items-center justify-center overflow-auto bg-[#333] p-8'>
<div className='max-w-[600px] rounded-xl bg-[#24292E] p-6 font-mono shadow backdrop-blur'>
<Story />
</div>
</div>
)
]
}
export const Vscode: StoryObj<TYPE_FC> = {
args: {
theme: 'vscode'
},
decorators: [
Story => (
<div className='flex h-full items-center justify-center overflow-auto bg-[#eee] p-8'>
<div className='max-w-[600px] rounded-xl bg-[#fff] p-6 font-mono shadow backdrop-blur'>
<Story />
</div>
</div>
)
]
}
export const Vscode_Dark: StoryObj<TYPE_FC> = {
args: {
theme: 'vscode',
dark: true
},
decorators: [
Story => (
<div className='flex h-full items-center justify-center overflow-auto bg-[#333] p-8'>
<div className='max-w-[600px] rounded-xl bg-[#1e1e1e] p-6 font-mono shadow backdrop-blur'>
<Story />
</div>
</div>
)
]
}
export const Atom: StoryObj<TYPE_FC> = {
args: {
theme: 'atom'
},
decorators: [
Story => (
<div className='flex h-full items-center justify-center overflow-auto bg-[#eee] p-8'>
<div className='max-w-[600px] rounded-xl bg-[#fff] p-6 font-mono shadow backdrop-blur'>
<Story />
</div>
</div>
)
]
}
export const Atom_Dark: StoryObj<TYPE_FC> = {
args: {
theme: 'atom',
dark: true
},
decorators: [
Story => (
<div className='flex h-full items-center justify-center overflow-auto bg-[#333] p-8'>
<div className='max-w-[600px] rounded-xl bg-[#282C34] p-6 font-mono shadow backdrop-blur'>
<Story />
</div>
</div>
)
]
}
export const Winter_is_Coming: StoryObj<TYPE_FC> = {
args: {
theme: 'winter-is-coming'
},
decorators: [
Story => (
<div className='flex h-full items-center justify-center overflow-auto bg-[#eee] p-8'>
<div className='max-w-[600px] rounded-xl bg-[#fff] p-6 font-mono shadow backdrop-blur'>
<Story />
</div>
</div>
)
]
}
export const Winter_is_Coming_Dark: StoryObj<TYPE_FC> = {
args: {
theme: 'winter-is-coming',
dark: true
},
decorators: [
Story => (
<div className='flex h-full items-center justify-center overflow-auto bg-[#333] p-8'>
<div className='max-w-[600px] rounded-xl bg-[#011627] p-6 font-mono shadow backdrop-blur'>
<Story />
</div>
</div>
)
]
}

View File

@@ -0,0 +1,148 @@
.json-view {
display: block;
color: #4d4d4d;
text-align: left;
--json-property: #009033;
--json-index: #676dff;
--json-number: #676dff;
--json-string: #b2762e;
--json-boolean: #dc155e;
--json-null: #dc155e;
}
.json-view .json-view--property {
color: var(--json-property);
}
.json-view .json-view--index {
color: var(--json-index);
}
.json-view .json-view--number {
color: var(--json-number);
}
.json-view .json-view--string {
color: var(--json-string);
}
.json-view .json-view--boolean {
color: var(--json-boolean);
}
.json-view .json-view--null {
color: var(--json-null);
}
.json-view .jv-indent {
padding-left: 1em;
}
.json-view .jv-chevron {
display: inline-block;
vertical-align: -20%;
cursor: pointer;
opacity: 0.4;
width: 1em;
height: 1em;
}
:is(.json-view .jv-chevron:hover, .json-view .jv-size:hover + .jv-chevron) {
opacity: 0.8;
}
.json-view .jv-size {
cursor: pointer;
opacity: 0.4;
font-size: 0.875em;
font-style: italic;
margin-left: 0.5em;
vertical-align: -5%;
line-height: 1;
}
.json-view :is(.json-view--copy, .json-view--edit),
.json-view .json-view--link svg {
display: none;
width: 1em;
height: 1em;
margin-left: 0.25em;
cursor: pointer;
}
.json-view .json-view--input {
width: 120px;
margin-left: 0.25em;
border-radius: 4px;
border: 1px solid currentColor;
padding: 0px 4px;
font-size: 87.5%;
line-height: 1.25;
background: transparent;
}
.json-view .json-view--deleting {
outline: 1px solid #da0000;
background-color: #da000011;
text-decoration-line: line-through;
}
:is(.json-view:hover, .json-view--pair:hover) > :is(.json-view--copy, .json-view--edit),
:is(.json-view:hover, .json-view--pair:hover) > .json-view--link svg {
display: inline-block;
}
.json-view .jv-button {
background: transparent;
outline: none;
border: none;
cursor: pointer;
color: inherit;
}
.json-view .cursor-pointer {
cursor: pointer;
}
.json-view svg {
vertical-align: -10%;
}
.jv-size-chevron ~ svg {
vertical-align: -16%;
}
/* Themes */
.json-view_a11y {
color: #545454;
--json-property: #aa5d00;
--json-index: #007299;
--json-number: #007299;
--json-string: #008000;
--json-boolean: #d91e18;
--json-null: #d91e18;
}
.json-view_github {
color: #005cc5;
--json-property: #005cc5;
--json-index: #005cc5;
--json-number: #005cc5;
--json-string: #032f62;
--json-boolean: #005cc5;
--json-null: #005cc5;
}
.json-view_vscode {
color: #005cc5;
--json-property: #0451a5;
--json-index: #0000ff;
--json-number: #0000ff;
--json-string: #a31515;
--json-boolean: #0000ff;
--json-null: #0000ff;
}
.json-view_atom {
color: #383a42;
--json-property: #e45649;
--json-index: #986801;
--json-number: #986801;
--json-string: #50a14f;
--json-boolean: #0184bc;
--json-null: #0184bc;
}
.json-view_winter-is-coming {
color: #0431fa;
--json-property: #3a9685;
--json-index: #ae408b;
--json-number: #ae408b;
--json-string: #8123a9;
--json-boolean: #0184bc;
--json-null: #0184bc;
}

View File

@@ -0,0 +1,8 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M12 3C10.22 3 8.47991 3.52784 6.99987 4.51677C5.51983 5.50571 4.36628 6.91131 3.68509 8.55585C3.0039 10.2004 2.82567 12.01 3.17294 13.7558C3.5202 15.5016 4.37737 17.1053 5.63604 18.364C6.89472 19.6226 8.49836 20.4798 10.2442 20.8271C11.99 21.1743 13.7996 20.9961 15.4442 20.3149C17.0887 19.6337 18.4943 18.4802 19.4832 17.0001C20.4722 15.5201 21 13.78 21 12C20.9974 9.61384 20.0484 7.32616 18.3611 5.63889C16.6738 3.95162 14.3862 3.00258 12 3ZM12 19.5C10.5166 19.5 9.0666 19.0601 7.83323 18.236C6.59986 17.4119 5.63856 16.2406 5.07091 14.8701C4.50325 13.4997 4.35473 11.9917 4.64411 10.5368C4.9335 9.08196 5.64781 7.74559 6.6967 6.6967C7.7456 5.64781 9.08197 4.9335 10.5368 4.64411C11.9917 4.35472 13.4997 4.50325 14.8701 5.0709C16.2406 5.63856 17.4119 6.59985 18.236 7.83322C19.0601 9.06659 19.5 10.5166 19.5 12C19.4978 13.9885 18.7069 15.8948 17.3009 17.3009C15.8948 18.7069 13.9885 19.4978 12 19.5Z"
fill="currentColor" />
<path
d="M15.5303 12.5303C15.671 12.3897 15.75 12.1989 15.75 12C15.75 11.8011 15.671 11.6103 15.5303 11.4697C15.3897 11.329 15.1989 11.25 15 11.25H12.75V9C12.75 8.80109 12.671 8.61032 12.5303 8.46967C12.3897 8.32902 12.1989 8.25 12 8.25C11.8011 8.25 11.6103 8.32902 11.4697 8.46967C11.329 8.61032 11.25 8.80109 11.25 9V11.25H9C8.80109 11.25 8.61032 11.329 8.46967 11.4697C8.32902 11.6103 8.25 11.8011 8.25 12C8.25 12.1989 8.32902 12.3897 8.46967 12.5303C8.61032 12.671 8.80109 12.75 9 12.75H11.25V15C11.25 15.1989 11.329 15.3897 11.4697 15.5303C11.6103 15.671 11.8011 15.75 12 15.75C12.1989 15.75 12.3897 15.671 12.5303 15.5303C12.671 15.3897 12.75 15.1989 12.75 15V12.75H15C15.1989 12.75 15.3897 12.671 15.5303 12.5303Z"
fill="#14C786" />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,8 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M21 6.75V17.25C21 19.3177 19.3177 21 17.25 21H6.75C4.68225 21 3 19.3177 3 17.25V6.75C3 4.68225 4.68225 3 6.75 3H17.25C19.3177 3 21 4.68225 21 6.75ZM19.5 6.75C19.5 5.5095 18.4905 4.5 17.25 4.5H6.75C5.5095 4.5 4.5 5.5095 4.5 6.75V17.25C4.5 18.4905 5.5095 19.5 6.75 19.5H17.25C18.4905 19.5 19.5 18.4905 19.5 17.25V6.75Z"
fill="currentColor" />
<path
d="M15 12.75C15.414 12.75 15.75 12.4148 15.75 12C15.75 11.5852 15.414 11.25 15 11.25H12.75V9C12.75 8.58525 12.414 8.25 12 8.25C11.586 8.25 11.25 8.58525 11.25 9V11.25H9C8.586 11.25 8.25 11.5852 8.25 12C8.25 12.4148 8.586 12.75 9 12.75H11.25V15C11.25 15.4148 11.586 15.75 12 15.75C12.414 15.75 12.75 15.4148 12.75 15V12.75H15Z"
fill="#14C786" />
</svg>

After

Width:  |  Height:  |  Size: 830 B

View File

@@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M12.4733 5.80615C12.4114 5.74366 12.3376 5.69406 12.2564 5.66022C12.1751 5.62637 12.088 5.60895 12 5.60895C11.912 5.60895 11.8249 5.62637 11.7436 5.66022C11.6624 5.69406 11.5886 5.74366 11.5267 5.80615L8.47333 8.85948C8.41136 8.92197 8.33762 8.97156 8.25639 9.00541C8.17515 9.03925 8.08801 9.05668 8 9.05668C7.91199 9.05668 7.82486 9.03925 7.74362 9.00541C7.66238 8.97156 7.58864 8.92197 7.52667 8.85948L4.47333 5.80615C4.41136 5.74366 4.33762 5.69406 4.25639 5.66022C4.17515 5.62637 4.08801 5.60895 4 5.60895C3.91199 5.60895 3.82486 5.62637 3.74362 5.66022C3.66238 5.69406 3.58864 5.74366 3.52667 5.80615C3.4025 5.93105 3.33281 6.10002 3.33281 6.27615C3.33281 6.45227 3.4025 6.62124 3.52667 6.74615L6.58667 9.80615C6.96167 10.1807 7.47 10.3911 8 10.3911C8.53 10.3911 9.03833 10.1807 9.41333 9.80615L12.4733 6.74615C12.5975 6.62124 12.6672 6.45227 12.6672 6.27615C12.6672 6.10002 12.5975 5.93105 12.4733 5.80615Z"
fill="currentColor" />
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,8 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M15 9C14.8593 8.8594 14.6686 8.78041 14.4697 8.78041C14.2708 8.78041 14.0801 8.8594 13.9395 9L12 10.9395L10.0605 9C9.91901 8.86338 9.72956 8.78779 9.53291 8.7895C9.33626 8.7912 9.14815 8.87008 9.0091 9.00914C8.87004 9.14819 8.79117 9.3363 8.78946 9.53295C8.78775 9.7296 8.86334 9.91905 8.99996 10.0605L10.9395 12L8.99996 13.9395C8.86334 14.081 8.78775 14.2704 8.78946 14.4671C8.79117 14.6637 8.87004 14.8518 9.0091 14.9909C9.14815 15.1299 9.33626 15.2088 9.53291 15.2105C9.72956 15.2122 9.91901 15.1366 10.0605 15L12 13.0605L13.9395 15C14.0809 15.1366 14.2704 15.2122 14.467 15.2105C14.6637 15.2088 14.8518 15.1299 14.9908 14.9909C15.1299 14.8518 15.2088 14.6637 15.2105 14.4671C15.2122 14.2704 15.1366 14.081 15 13.9395L13.0605 12L15 10.0605C15.1406 9.91985 15.2196 9.72912 15.2196 9.53025C15.2196 9.33138 15.1406 9.14065 15 9Z"
fill="#DA0000" />
<path
d="M12 3C10.22 3 8.47991 3.52784 6.99987 4.51677C5.51983 5.50571 4.36628 6.91131 3.68509 8.55585C3.0039 10.2004 2.82567 12.01 3.17294 13.7558C3.5202 15.5016 4.37737 17.1053 5.63604 18.364C6.89472 19.6226 8.49836 20.4798 10.2442 20.8271C11.99 21.1743 13.7996 20.9961 15.4442 20.3149C17.0887 19.6337 18.4943 18.4802 19.4832 17.0001C20.4722 15.5201 21 13.78 21 12C20.9974 9.61384 20.0484 7.32616 18.3611 5.63889C16.6738 3.95162 14.3862 3.00258 12 3ZM12 19.5C10.5166 19.5 9.0666 19.0601 7.83323 18.236C6.59986 17.4119 5.63856 16.2406 5.07091 14.8701C4.50325 13.4997 4.35473 11.9917 4.64411 10.5368C4.9335 9.08197 5.64781 7.74559 6.6967 6.6967C7.7456 5.64781 9.08197 4.9335 10.5368 4.64411C11.9917 4.35472 13.4997 4.50325 14.8701 5.0709C16.2406 5.63856 17.4119 6.59985 18.236 7.83322C19.0601 9.06659 19.5 10.5166 19.5 12C19.4978 13.9885 18.7069 15.8948 17.3009 17.3009C15.8948 18.7069 13.9885 19.4978 12 19.5Z"
fill="currentColor" />
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,8 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M17.25 3H6.75C5.7558 3.00119 4.80267 3.39666 4.09966 4.09966C3.39666 4.80267 3.00119 5.7558 3 6.75L3 17.25C3.00119 18.2442 3.39666 19.1973 4.09966 19.9003C4.80267 20.6033 5.7558 20.9988 6.75 21H17.25C18.2442 20.9988 19.1973 20.6033 19.9003 19.9003C20.6033 19.1973 20.9988 18.2442 21 17.25V6.75C20.9988 5.7558 20.6033 4.80267 19.9003 4.09966C19.1973 3.39666 18.2442 3.00119 17.25 3ZM19.5 17.25C19.5 17.8467 19.2629 18.419 18.841 18.841C18.419 19.2629 17.8467 19.5 17.25 19.5H6.75C6.15326 19.5 5.58097 19.2629 5.15901 18.841C4.73705 18.419 4.5 17.8467 4.5 17.25V6.75C4.5 6.15326 4.73705 5.58097 5.15901 5.15901C5.58097 4.73705 6.15326 4.5 6.75 4.5H17.25C17.8467 4.5 18.419 4.73705 18.841 5.15901C19.2629 5.58097 19.5 6.15326 19.5 6.75V17.25Z"
fill="currentColor" />
<path
d="M10.3116 14.4507L7.83053 11.9047C7.71181 11.7829 7.55082 11.7145 7.38295 11.7145C7.21507 11.7145 7.05408 11.7829 6.93536 11.9047C6.81667 12.0266 6.75 12.1918 6.75 12.364C6.75 12.5363 6.81667 12.7015 6.93536 12.8233L9.4164 15.3693C9.53398 15.49 9.67358 15.5857 9.82723 15.651C9.98089 15.7164 10.1456 15.75 10.3119 15.75C10.4782 15.75 10.6429 15.7164 10.7965 15.651C10.9502 15.5857 11.0898 15.49 11.2074 15.3693L17.0646 9.3588C17.1833 9.23697 17.25 9.07176 17.25 8.8995C17.25 8.72724 17.1833 8.56203 17.0646 8.44021C16.9459 8.31842 16.7849 8.25 16.6171 8.25C16.4492 8.25 16.2882 8.31842 16.1695 8.44021L10.3116 14.4507Z"
fill="#14C786" />
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M17.5417 2.5H12.7917C10.609 2.5 8.83333 4.27571 8.83333 6.45833V11.2083C8.83333 13.391 10.609 15.1667 12.7917 15.1667H17.5417C19.7243 15.1667 21.5 13.391 21.5 11.2083V6.45833C21.5 4.27571 19.7243 2.5 17.5417 2.5ZM19.9167 11.2083C19.9167 12.5178 18.8511 13.5833 17.5417 13.5833H12.7917C11.4823 13.5833 10.4167 12.5178 10.4167 11.2083V6.45833C10.4167 5.14892 11.4823 4.08333 12.7917 4.08333H17.5417C18.8511 4.08333 19.9167 5.14892 19.9167 6.45833V11.2083ZM15.1667 17.5417C15.1667 19.7243 13.391 21.5 11.2083 21.5H6.45833C4.27571 21.5 2.5 19.7243 2.5 17.5417V12.7917C2.5 10.609 4.27571 8.83333 6.45833 8.83333C6.89613 8.83333 7.25 9.188 7.25 9.625C7.25 10.062 6.89613 10.4167 6.45833 10.4167C5.14892 10.4167 4.08333 11.4823 4.08333 12.7917V17.5417C4.08333 18.8511 5.14892 19.9167 6.45833 19.9167H11.2083C12.5178 19.9167 13.5833 18.8511 13.5833 17.5417C13.5833 17.1047 13.9372 16.75 14.375 16.75C14.8128 16.75 15.1667 17.1047 15.1667 17.5417Z"
fill="currentColor" />
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="512" height="512">
<path
d="m23.34,9.481l-3.5-6c-.893-1.53-2.548-2.481-4.319-2.481h-7.069c-1.771,0-3.427.951-4.319,2.481L.632,9.481c-.905,1.553-.905,3.483,0,5.038l3.501,6c.893,1.53,2.547,2.48,4.318,2.48h7.069c1.771,0,3.426-.95,4.319-2.48l3.5-6.001c.905-1.554.905-3.484,0-5.038Zm-7.34,3.519h-3v3c0,.553-.447,1-1,1s-1-.447-1-1v-3h-3c-.553,0-1-.448-1-1s.447-1,1-1h3v-3c0-.552.447-1,1-1s1,.448,1,1v3h3c.553,0,1,.448,1,1s-.447,1-1,1Z" />
</svg>

After

Width:  |  Height:  |  Size: 515 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve" width="512" height="512">
<path
d="M405.333,0H106.667C47.786,0.071,0.071,47.786,0,106.667v298.667C0.071,464.214,47.786,511.93,106.667,512h298.667 C464.214,511.93,511.93,464.214,512,405.333V106.667C511.93,47.786,464.214,0.071,405.333,0z M426.667,172.352L229.248,369.771 c-16.659,16.666-43.674,16.671-60.34,0.012c-0.004-0.004-0.008-0.008-0.012-0.012l-83.563-83.541 c-8.348-8.348-8.348-21.882,0-30.229s21.882-8.348,30.229,0l83.541,83.541l197.44-197.419c8.348-8.318,21.858-8.294,30.176,0.053 C435.038,150.524,435.014,164.034,426.667,172.352z" />
</svg>

After

Width:  |  Height:  |  Size: 763 B

View File

@@ -0,0 +1 @@
<svg height="512" viewBox="0 0 24 24" width="512" xmlns="http://www.w3.org/2000/svg" data-name="Layer 1"><path d="m15 20h-10a5.006 5.006 0 0 1 -5-5v-10a5.006 5.006 0 0 1 5-5h10a5.006 5.006 0 0 1 5 5v10a5.006 5.006 0 0 1 -5 5zm9-1v-13a1 1 0 0 0 -2 0v13a3 3 0 0 1 -3 3h-13a1 1 0 0 0 0 2h13a5.006 5.006 0 0 0 5-5z"/></svg>

After

Width:  |  Height:  |  Size: 320 B

View File

@@ -0,0 +1,8 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M12 3C10.22 3 8.47991 3.52784 6.99987 4.51677C5.51983 5.50571 4.36628 6.91131 3.68509 8.55585C3.0039 10.2004 2.82567 12.01 3.17294 13.7558C3.5202 15.5016 4.37737 17.1053 5.63604 18.364C6.89472 19.6226 8.49836 20.4798 10.2442 20.8271C11.99 21.1743 13.7996 20.9961 15.4442 20.3149C17.0887 19.6337 18.4943 18.4802 19.4832 17.0001C20.4722 15.5201 21 13.78 21 12C20.9974 9.61384 20.0484 7.32616 18.3611 5.63889C16.6738 3.95162 14.3862 3.00258 12 3ZM12 19.5C10.5166 19.5 9.0666 19.0601 7.83323 18.236C6.59986 17.4119 5.63856 16.2406 5.07091 14.8701C4.50325 13.4997 4.35473 11.9917 4.64411 10.5368C4.9335 9.08196 5.64781 7.74559 6.6967 6.6967C7.7456 5.64781 9.08197 4.9335 10.5368 4.64411C11.9917 4.35472 13.4997 4.50325 14.8701 5.0709C16.2406 5.63856 17.4119 6.59985 18.236 7.83322C19.0601 9.06659 19.5 10.5166 19.5 12C19.4978 13.9885 18.7069 15.8948 17.3009 17.3009C15.8948 18.7069 13.9885 19.4978 12 19.5Z"
fill="currentColor" />
<path
d="M10.8493 13.9605L8.86443 11.9238C8.76945 11.8264 8.64065 11.7716 8.50636 11.7716C8.37206 11.7716 8.24326 11.8264 8.14829 11.9238C8.05334 12.0212 8 12.1534 8 12.2912C8 12.429 8.05334 12.5612 8.14829 12.6587L10.1331 14.6954C10.2272 14.792 10.3389 14.8686 10.4618 14.9208C10.5847 14.9731 10.7165 15 10.8495 15C10.9826 15 11.1143 14.9731 11.2372 14.9208C11.3602 14.8686 11.4718 14.792 11.5659 14.6954L16.2517 9.88704C16.3467 9.78958 16.4 9.65741 16.4 9.5196C16.4 9.38179 16.3467 9.24962 16.2517 9.15216C16.1567 9.05473 16.0279 9 15.8936 9C15.7593 9 15.6306 9.05473 15.5356 9.15216L10.8493 13.9605Z"
fill="#14C786" />
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M17.25 3H6.75C4.68225 3 3 4.68225 3 6.75V17.25C3 19.3177 4.68225 21 6.75 21H17.25C19.3177 21 21 19.3177 21 17.25V6.75C21 4.68225 19.3177 3 17.25 3ZM19.5 17.25C19.5 18.4905 18.4905 19.5 17.25 19.5H6.75C5.5095 19.5 4.5 18.4905 4.5 17.25V6.75C4.5 5.5095 5.5095 4.5 6.75 4.5H17.25C18.4905 4.5 19.5 5.5095 19.5 6.75V17.25ZM13.4092 7.78425L8.379 12.8145C7.81275 13.3808 7.5 14.1345 7.5 14.9355V16.125C7.5 16.5397 7.836 16.875 8.25 16.875H9.4395C10.2405 16.875 10.9942 16.563 11.5605 15.996L16.5908 10.9658C17.4683 10.0883 17.4683 8.66175 16.5908 7.78425C15.7403 6.9345 14.259 6.9345 13.4092 7.78425ZM10.5 14.9355C10.2203 15.2145 9.834 15.375 9.4395 15.375H9V14.9355C9 14.535 9.156 14.1585 9.4395 13.875L12.627 10.6875L13.6875 11.748L10.5 14.9355ZM15.5303 9.90525L14.748 10.6875L13.6875 9.627L14.4698 8.84475C14.7525 8.5605 15.2468 8.5605 15.5303 8.84475C15.8228 9.13725 15.8228 9.61275 15.5303 9.90525Z"
fill="currentColor" />
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.75 3H12.25V4.5H6.75C5.5095 4.5 4.5 5.5095 4.5 6.75V17.25C4.5 18.4905 5.5095 19.5 6.75 19.5H17.25C18.4905 19.5 19.5 18.4905 19.5 17.25V11.75H21V17.25C21 19.3177 19.3177 21 17.25 21H6.75C4.68225 21 3 19.3177 3 17.25V6.75C3 4.68225 4.68225 3 6.75 3Z" fill="currentColor"/>
<path d="M20.0134 3H16.0669C15.8053 3.00003 15.5544 3.10399 15.3694 3.28902C15.1844 3.47404 15.0804 3.72497 15.0804 3.98662C15.0804 4.24827 15.1844 4.4992 15.3694 4.68422C15.5544 4.86925 15.8053 4.97321 16.0669 4.97324H17.6307L11.2892 11.3148C10.9108 11.6932 10.9147 12.3364 11.2892 12.7109C11.6675 13.0892 12.3107 13.0854 12.6852 12.7109L19.0268 6.36931V7.9331C19.0268 8.19475 19.1308 8.44567 19.3158 8.63067C19.5008 8.81567 19.7518 8.9196 20.0134 8.9196C20.2751 8.9196 20.526 8.81567 20.711 8.63067C20.896 8.44567 21 8.19475 21 7.9331V3.98662C21 3.72495 20.8961 3.474 20.7111 3.28897C20.526 3.10395 20.2751 3 20.0134 3Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,11 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M18.75 6H16.425C16.2509 5.15356 15.7904 4.39301 15.1209 3.84654C14.4515 3.30007 13.6142 3.00109 12.75 3L11.25 3C10.3858 3.00109 9.54849 3.30007 8.87906 3.84654C8.20963 4.39301 7.74907 5.15356 7.575 6H5.25C5.05109 6 4.86032 6.07902 4.71967 6.21967C4.57902 6.36032 4.5 6.55109 4.5 6.75C4.5 6.94891 4.57902 7.13968 4.71967 7.28033C4.86032 7.42098 5.05109 7.5 5.25 7.5H6V17.25C6.00119 18.2442 6.39666 19.1973 7.09966 19.9003C7.80267 20.6033 8.7558 20.9988 9.75 21H14.25C15.2442 20.9988 16.1973 20.6033 16.9003 19.9003C17.6033 19.1973 17.9988 18.2442 18 17.25V7.5H18.75C18.9489 7.5 19.1397 7.42098 19.2803 7.28033C19.421 7.13968 19.5 6.94891 19.5 6.75C19.5 6.55109 19.421 6.36032 19.2803 6.21967C19.1397 6.07902 18.9489 6 18.75 6ZM11.25 4.5H12.75C13.2152 4.50057 13.6688 4.64503 14.0487 4.91358C14.4286 5.18213 14.7161 5.56162 14.8717 6H9.12825C9.28394 5.56162 9.57143 5.18213 9.95129 4.91358C10.3312 4.64503 10.7848 4.50057 11.25 4.5ZM16.5 17.25C16.5 17.8467 16.2629 18.419 15.841 18.841C15.419 19.2629 14.8467 19.5 14.25 19.5H9.75C9.15326 19.5 8.58097 19.2629 8.15901 18.841C7.73705 18.419 7.5 17.8467 7.5 17.25V7.5H16.5V17.25Z"
fill="currentColor" />
<path
d="M10.5 16.5C10.6989 16.5 10.8897 16.421 11.0303 16.2803C11.171 16.1397 11.25 15.9489 11.25 15.75V11.25C11.25 11.0511 11.171 10.8603 11.0303 10.7197C10.8897 10.579 10.6989 10.5 10.5 10.5C10.3011 10.5 10.1103 10.579 9.96967 10.7197C9.82902 10.8603 9.75 11.0511 9.75 11.25V15.75C9.75 15.9489 9.82902 16.1397 9.96967 16.2803C10.1103 16.421 10.3011 16.5 10.5 16.5Z"
fill="#DA0000" />
<path
d="M13.5 16.5C13.6989 16.5 13.8897 16.421 14.0303 16.2803C14.171 16.1397 14.25 15.9489 14.25 15.75V11.25C14.25 11.0511 14.171 10.8603 14.0303 10.7197C13.8897 10.579 13.6989 10.5 13.5 10.5C13.3011 10.5 13.1103 10.579 12.9697 10.7197C12.829 10.8603 12.75 11.0511 12.75 11.25V15.75C12.75 15.9489 12.829 16.1397 12.9697 16.2803C13.1103 16.421 13.3011 16.5 13.5 16.5Z"
fill="#DA0000" />
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,34 @@
export declare type Collapsed =
| undefined
| number
| boolean
| ((params: { node: Record<string, any> | Array<any>; indexOrName: number | string | undefined; depth: number; size: number }) => boolean | void)
export type DisplaySize = undefined | number | boolean | 'collapsed' | 'expanded'
export declare type Editable =
| boolean
| {
add?: boolean
edit?: boolean
delete?: boolean
}
export declare type CustomizeOptions = {
add?: boolean
edit?: boolean
delete?: boolean
enableClipboard?: boolean
matchesURL?: boolean
collapsed?: boolean
className?: string
}
export declare type CustomizeNode = (params: {
node: any
indexOrName: number | string | undefined
depth: number
}) => CustomizeOptions | React.FC | React.Component | React.ReactElement<any, any> | undefined
export type CustomizeCollapseStringUI = ((str_show: string, truncated: boolean) => JSX.Element | string) | string
export type NodeMeta = { depth: number; indexOrName?: number | string; parent?: Record<string, any> | Array<any>; parentPath: string[], currentPath: string[] }

View File

@@ -0,0 +1,154 @@
import type { Collapsed, CustomizeOptions, DisplaySize, Editable } from './types'
import copy from 'copy-to-clipboard'
export function isObject(node: any): node is Record<string, any> {
return Object.prototype.toString.call(node) === '[object Object]'
}
export function objectSize(node: Record<string, any> | Array<any>) {
return Array.isArray(node) ? node.length : isObject(node) ? Object.keys(node).length : 0
}
export function stringifyForCopying(node: any, space?: string | number | undefined) {
// return single string nodes without quotes
if (typeof node === 'string') {
return node
}
try {
return JSON.stringify(
node,
(key, value) => {
switch (typeof value) {
case 'bigint':
return String(value) + 'n'
case 'number':
case 'boolean':
case 'object':
case 'string':
return value
default:
return String(value)
}
},
space
)
} catch (error: any) {
return `${error.name}: ${error.message}` || 'JSON.stringify failed'
}
}
export async function writeClipboard(value: string) {
try {
await navigator.clipboard.writeText(value)
} catch (err) {
copy(value)
}
}
export function isCollapsed(
node: Record<string, any> | Array<any>,
depth: number,
indexOrName: number | string | undefined,
collapsed: Collapsed,
collapseObjectsAfterLength: number,
customOptions?: CustomizeOptions
): boolean {
if (customOptions && customOptions.collapsed !== undefined) return !!customOptions.collapsed
if (typeof collapsed === 'boolean') return collapsed
if (typeof collapsed === 'number' && depth > collapsed) return true
const size = objectSize(node)
if (typeof collapsed === 'function') {
const result = safeCall(collapsed, [{ node, depth, indexOrName, size }])
if (typeof result === 'boolean') return result
}
if (Array.isArray(node) && size > collapseObjectsAfterLength) return true
if (isObject(node) && size > collapseObjectsAfterLength) return true
return false
}
export function isCollapsed_largeArray(
node: Record<string, any> | Array<any>,
depth: number,
indexOrName: number | string | undefined,
collapsed: Collapsed,
collapseObjectsAfterLength: number,
customOptions?: CustomizeOptions
): boolean {
if (customOptions && customOptions.collapsed !== undefined) return !!customOptions.collapsed
if (typeof collapsed === 'boolean') return collapsed
if (typeof collapsed === 'number' && depth > collapsed) return true
const size = Math.ceil(node.length / 100)
if (typeof collapsed === 'function') {
const result = safeCall(collapsed, [{ node, depth, indexOrName, size }])
if (typeof result === 'boolean') return result
}
if (Array.isArray(node) && size > collapseObjectsAfterLength) return true
if (isObject(node) && size > collapseObjectsAfterLength) return true
return false
}
export function ifDisplay(displaySize: DisplaySize, depth: number, fold: boolean) {
if (typeof displaySize === 'boolean') return displaySize
if (typeof displaySize === 'number' && depth > displaySize) return true
if (displaySize === 'collapsed' && fold) return true
if (displaySize === 'expanded' && !fold) return true
return false
}
export function safeCall<T extends (...args: any[]) => any>(func: T, params: Parameters<T>) {
try {
return func(...params)
} catch (event) {
reportError(event)
}
}
export function editableAdd(editable: Editable) {
if (editable === true) return true
if (isObject(editable) && (editable as { add: boolean }).add === true) return true
}
export function editableEdit(editable: Editable) {
if (editable === true) return true
if (isObject(editable) && (editable as { edit: boolean }).edit === true) return true
}
export function editableDelete(editable: Editable) {
if (editable === true) return true
if (isObject(editable) && (editable as { delete: boolean }).delete === true) return true
}
function isClassComponent(component: any) {
return typeof component === 'function' && !!component.prototype?.isReactComponent
}
export function isReactComponent(component: any): component is (new () => React.Component<any, any>) | React.FC<any> {
return typeof component === 'function'
}
export function customAdd(customOptions?: CustomizeOptions) {
return !customOptions || customOptions.add === undefined || !!customOptions.add
}
export function customEdit(customOptions?: CustomizeOptions) {
return !customOptions || customOptions.edit === undefined || !!customOptions.edit
}
export function customDelete(customOptions?: CustomizeOptions) {
return !customOptions || customOptions.delete === undefined || !!customOptions.delete
}
export function customCopy(customOptions?: CustomizeOptions) {
return !customOptions || customOptions.enableClipboard === undefined || !!customOptions.enableClipboard
}
export function customMatchesURL(customOptions?: CustomizeOptions) {
return !customOptions || customOptions.matchesURL === undefined || !!customOptions.matchesURL
}
export function resolveEvalFailedNewValue(type: string, value: string) {
if (type === 'string') {
return value.trim().replace(/^\"([\s\S]+?)\"$/, '$1')
}
return value
}