├── .README_npm.md
├── .github
├── FUNDING.yml
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── .gitignore
├── .npmignore
├── .prettierrc
├── LICENSE
├── README.md
├── custom-component-library
├── .gitignore
├── README.md
├── components
│ ├── BigInt
│ │ ├── component.tsx
│ │ ├── definition.ts
│ │ └── index.ts
│ ├── BooleanToggle
│ │ ├── component.tsx
│ │ ├── definition.ts
│ │ └── index.ts
│ ├── DateObject
│ │ ├── component.tsx
│ │ ├── definition.ts
│ │ └── index.ts
│ ├── DatePicker
│ │ ├── Button.tsx
│ │ ├── component.tsx
│ │ ├── definition.ts
│ │ ├── index.ts
│ │ └── style.css
│ ├── EnhancedLink
│ │ ├── component.tsx
│ │ ├── definition.ts
│ │ └── index.ts
│ ├── Hyperlink
│ │ ├── component.tsx
│ │ ├── definition.ts
│ │ └── index.ts
│ ├── Markdown
│ │ ├── component.tsx
│ │ ├── definition.ts
│ │ └── index.ts
│ ├── NaN
│ │ ├── component.tsx
│ │ ├── definition.ts
│ │ └── index.ts
│ ├── Symbol
│ │ ├── component.tsx
│ │ ├── definition.ts
│ │ └── index.ts
│ ├── Undefined
│ │ ├── component.tsx
│ │ ├── definition.ts
│ │ └── index.ts
│ ├── data.ts
│ └── index.ts
├── eslint.config.js
├── image
│ └── library_screenshot.png
├── index.html
├── package.json
├── public
│ └── favicon_96.png
├── src
│ ├── App.tsx
│ ├── index.css
│ ├── main.tsx
│ └── vite-env.d.ts
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
└── yarn.lock
├── demo
├── .gitignore
├── README.md
├── eslint.config.js
├── index.html
├── package.json
├── public
│ ├── favicon.ico
│ └── image
│ │ ├── apple_touch_icon_180.png
│ │ ├── favicon_16.png
│ │ ├── favicon_32.png
│ │ ├── favicon_96.png
│ │ ├── icon_192.png
│ │ ├── icon_512.png
│ │ ├── json-edit-react_logo.png
│ │ ├── logo192.png
│ │ └── social_banner.png
├── scripts
│ ├── output.json
│ └── scrape_swapi.mjs
├── src
│ ├── App.tsx
│ ├── CodeEditor.tsx
│ ├── LazyThemes.ts
│ ├── SourceIndicator.tsx
│ ├── chakra-theme
│ │ ├── colours.ts
│ │ ├── components.ts
│ │ ├── fonts.ts
│ │ ├── index.ts
│ │ └── styles.css
│ ├── demoData
│ │ ├── customNodesSchema.json
│ │ ├── data.tsx
│ │ ├── dataDefinitions.tsx
│ │ ├── index.ts
│ │ ├── jsonSchema.json
│ │ └── superheroExample.json5
│ ├── helpers.ts
│ ├── image
│ │ ├── logo.dark.svg
│ │ ├── logo.svg
│ │ ├── logo_400.png
│ │ ├── logo_square.dark.svg
│ │ └── logo_square.svg
│ ├── index.css
│ ├── main.tsx
│ ├── react-app-env.d.ts
│ ├── style.css
│ ├── useDatabase.ts
│ └── vite-env.d.ts
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
└── yarn.lock
├── eslint.config.mjs
├── image
├── admonition_npm.png
├── custom_component_levels.png
├── custom_text.png
├── enum_example.png
├── json-edit-react-logo.svg
├── key_select.png
├── logo192.png
├── screenshot.png
├── text-editor-comparison.png
├── theme_edit_after.png
└── theme_edit_before.png
├── package.json
├── rollup.config.mjs
├── scripts
├── build_npm_readme.py
├── cleanBuildTypes.cjs
├── installLatestPackage.mjs
└── use_npm_readme.py
├── src
├── AutogrowTextArea.tsx
├── ButtonPanels.tsx
├── CollectionNode.tsx
├── CustomNode.ts
├── Icons.tsx
├── JsonEditor.tsx
├── KeyDisplay.tsx
├── ValueNodeWrapper.tsx
├── ValueNodes.tsx
├── additionalThemes
│ └── index.ts
├── contexts
│ ├── ThemeProvider.tsx
│ ├── TreeStateProvider.tsx
│ └── index.ts
├── customComponents
│ ├── ActiveHyperlinks.tsx
│ └── index.ts
├── helpers.ts
├── hooks
│ ├── index.ts
│ ├── useCollapseTransition.ts
│ ├── useCommon.ts
│ ├── useData.ts
│ ├── useDragNDrop.tsx
│ └── useTriggers.ts
├── index.ts
├── localisation.ts
├── style.css
└── types.ts
├── test
└── nextPrevious.test.ts
├── tsconfig.json
└── yarn.lock
/.README_npm.md:
--------------------------------------------------------------------------------
1 |
8 |
9 | {{NPM INTRO}}
10 |
11 |
12 |
13 | {{NPM USAGE}}
14 |
15 | ---
16 |
17 | For **FULL DOCUMENTATION**, visit [https://github.com/CarlosNZ/json-edit-react](https://github.com/CarlosNZ/json-edit-react#json-edit-react)
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: CarlosNZ
4 | # patreon: # Replace with a single Patreon username
5 | # open_collective: # Replace with a single Open Collective username
6 | # ko_fi: # Replace with a single Ko-fi username
7 | # tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | # community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | # liberapay: # Replace with a single Liberapay username
10 | # issuehunt: # Replace with a single IssueHunt username
11 | # lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
12 | # polar: # Replace with a single Polar username
13 | buy_me_a_coffee: carlsmith
14 | # thanks_dev: # Replace with a single thanks.dev username
15 | # custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
16 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Reporting bugs for json-edit-react
4 | title: Bug report
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 |
12 |
13 | **Expected behavior**
14 |
15 |
16 | **Screenshots**
17 |
18 |
19 | **Online demo**
20 |
24 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Request new features or enhancements
4 | title: Feature request
5 | labels: "user request"
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Suggestion**
11 |
12 |
13 | **Use case**
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | #Build folder
2 | build/
3 |
4 | # Logs
5 | logs
6 | *.log
7 | npm-debug.log*
8 | yarn-debug.log*
9 | yarn-error.log*
10 | lerna-debug.log*
11 |
12 | # Compiled binary addons (https://nodejs.org/api/addons.html)
13 | build/Release
14 |
15 | # Dependency directories
16 | node_modules/
17 |
18 | # TypeScript v1 declaration files
19 | typings/
20 |
21 | # TypeScript cache
22 | *.tsbuildinfo
23 |
24 | # Optional npm cache directory
25 | .npm
26 |
27 | # Optional eslint cache
28 | .eslintcache
29 |
30 | # Yarn Integrity file
31 | .yarn-integrity
32 |
33 | # dotenv environment variables file
34 | .env
35 | .env.test
36 |
37 | #Misc
38 | .DS_Store
39 |
40 | # Duplicated source code for GUI app
41 | demo/src/json-edit-react
42 |
43 | # Dev playground file
44 | .npmrc
45 | temp.js
46 | .vscode/settings.json
47 | demo/src/firebaseConfig.json
48 |
49 | # Built package in demo/custom library
50 | build_package
51 | demo/src/package
52 | custom-component-library/components/package
53 | .original-readme.md
54 | .npm-readme.md
55 | demo/src/imports/import.ts
56 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | README_npm.md
2 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "bracketSpacing": true,
3 | "printWidth": 100,
4 | "singleQuote": true,
5 | "tabWidth": 2,
6 | "trailingComma": "es5",
7 | "semi": false
8 | }
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Carl Smith
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/custom-component-library/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/custom-component-library/README.md:
--------------------------------------------------------------------------------
1 | ## Custom Component Library
2 |
3 | A collection of [Custom Components](https://github.com/CarlosNZ/json-edit-react#custom-nodes) for **json-edit-react**.
4 |
5 | You can use these as-is or modify them for your own particular use case.
6 |
7 |
8 |
9 | Eventually, I'd like to publish these in a separate package so you can easily import them. But for now just copy the code out of this repo.
10 |
11 | Contains a [Vite](https://vite.dev/) web-app for previewing and developing components.
12 |
13 | The individual components are in the `/components` folder, along with demo data (in `data.ts`).
14 |
15 | > [!NOTE]
16 | > If you create a custom component that you think would be useful to others, please [create a PR](https://github.com/CarlosNZ/json-edit-react/pulls) for it.
17 |
18 | ## Components
19 |
20 | These are the ones currently available:
21 |
22 | - [x] Hyperlink/URL
23 | - [x] Undefined
24 | - [x] `Date` Object
25 | - [x] Date/Time Picker (with ISO string)
26 | - [x] Boolean Toggle
27 | - [x] `NaN`
28 | - [x] `BigInt`
29 | - [x] Markdown
30 | - [x] "Enhanced" link
31 | - [ ] Image (to-do)
32 |
33 | ## Development
34 |
35 | From within this folder: `/custom-component-library`:
36 |
37 | Install dependencies:
38 |
39 | ```js
40 | yarn install
41 | ```
42 |
43 | Launch app:
44 |
45 | ```js
46 | yarn dev
47 | ```
48 |
49 | ## Guidelines for development:
50 |
51 | Custom components should consider the following:
52 |
53 | - Must respect editing restrictions
54 | - If including CSS classes, please prefix with `jer-`
55 | - Handle keyboard input as much as possible:
56 | - Double-click to edit (if allowed)
57 | - `Tab`/`Shift-Tab` to navigate
58 | - `Enter` to submit
59 | - `Escape` to cancel
60 | - Provide customisation options, particularly styles
61 | - If the data contains non-JSON types, add a "stringify" and "reviver" function definition (see `BigInt`, `NaN` and `Symbol` components)
62 |
63 | If your custom component is "string-like", there are two helper components exported with the package: `StringDisplay` and `StringEdit` -- these are the same components used for the actual "string" elements in the main package. See the [Hyperlink](https://github.com/CarlosNZ/json-edit-react/blob/main/custom-component-library/components/Hyperlink/component.tsx) and [BigInt](https://github.com/CarlosNZ/json-edit-react/blob/main/custom-component-library/components/BigInt/component.tsx) components for examples of how to use them.
64 |
65 |
--------------------------------------------------------------------------------
/custom-component-library/components/BigInt/component.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { toPathString, StringEdit, type CustomNodeProps } from '@json-edit-react'
3 |
4 | export interface BigIntProps {
5 | style?: React.CSSProperties
6 | descriptionStyle?: React.CSSProperties
7 | }
8 |
9 | export const BigIntComponent: React.FC> = (props) => {
10 | const {
11 | setValue,
12 | isEditing,
13 | setIsEditing,
14 | getStyles,
15 | nodeData,
16 | customNodeProps = {},
17 | value,
18 | handleEdit,
19 | ...rest
20 | } = props
21 | const { path } = nodeData
22 | const { style = { color: '#006291', fontSize: '90%' } } = customNodeProps
23 |
24 | const editDisplayValue = typeof value === 'bigint' ? String(value) : (value as string)
25 |
26 | return isEditing ? (
27 | >}
32 | {...rest}
33 | handleEdit={() => {
34 | handleEdit(BigInt(nodeData.value as string))
35 | }}
36 | />
37 | ) : (
38 | setIsEditing(true)} style={style}>
39 | {value as bigint}
40 |
41 | )
42 | }
43 |
--------------------------------------------------------------------------------
/custom-component-library/components/BigInt/definition.ts:
--------------------------------------------------------------------------------
1 | import { isCollection, type CustomNodeDefinition } from '@json-edit-react'
2 | import { BigIntComponent, BigIntProps } from './component'
3 |
4 | export const BigIntDefinition: CustomNodeDefinition = {
5 | condition: ({ value }) => typeof value === 'bigint',
6 | element: BigIntComponent,
7 | // customNodeProps: {},
8 | showOnView: true,
9 | showEditTools: true,
10 | showOnEdit: true,
11 | name: 'BigInt', // shown in the Type selector menu
12 | showInTypesSelector: true,
13 | defaultValue: BigInt(9007199254740992),
14 | stringifyReplacer: (value) =>
15 | typeof value === 'bigint' ? { __type: 'bigint', value: String(value) } : value,
16 | parseReviver: (value) =>
17 | isCollection(value) && '__type' in value && 'value' in value && value.__type === 'bigint'
18 | ? BigInt(value.value as string)
19 | : value,
20 | }
21 |
--------------------------------------------------------------------------------
/custom-component-library/components/BigInt/index.ts:
--------------------------------------------------------------------------------
1 | export * from './definition'
2 |
--------------------------------------------------------------------------------
/custom-component-library/components/BooleanToggle/component.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Boolean Toggle
3 | *
4 | * - Provides an alternative to the default boolean input -- a checkbox that can
5 | * be toggled on and off without having to explicity enable "editing" of the
6 | * element.
7 | */
8 |
9 | import React from 'react'
10 | import { toPathString, type CustomNodeProps } from '@json-edit-react'
11 |
12 | export const BooleanToggleComponent: React.FC = (props) => {
13 | const { nodeData, value, handleEdit, canEdit } = props
14 | const { path } = nodeData
15 | return (
16 | {
23 | // In this case we submit the data value immediately, not just the local
24 | // state
25 | handleEdit(!nodeData.value)
26 | // setValue(!value)
27 | }}
28 | />
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/custom-component-library/components/BooleanToggle/definition.ts:
--------------------------------------------------------------------------------
1 | import { type CustomNodeDefinition } from '@json-edit-react'
2 | import { BooleanToggleComponent } from './component'
3 |
4 | export const BooleanToggleDefinition: CustomNodeDefinition<{
5 | linkStyles?: React.CSSProperties
6 | stringTruncate?: number
7 | }> = {
8 | condition: ({ value }) => typeof value === 'boolean',
9 | element: BooleanToggleComponent,
10 | showOnView: true,
11 | showOnEdit: false,
12 | showEditTools: true,
13 | }
14 |
--------------------------------------------------------------------------------
/custom-component-library/components/BooleanToggle/index.ts:
--------------------------------------------------------------------------------
1 | export * from './definition'
2 |
--------------------------------------------------------------------------------
/custom-component-library/components/DateObject/component.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef } from 'react'
2 | import { StringDisplay, toPathString, StringEdit, type CustomNodeProps } from '@json-edit-react'
3 |
4 | export interface DateObjectProps {
5 | showTime?: boolean
6 | }
7 |
8 | export const DateObjectCustomComponent: React.FC> = (props) => {
9 | const {
10 | nodeData,
11 | isEditing,
12 | setValue,
13 | getStyles,
14 | canEdit,
15 | value,
16 | handleEdit,
17 | onError,
18 | customNodeProps = {},
19 | } = props
20 | const lastValidDate = useRef(value)
21 |
22 | const { showTime = true } = customNodeProps
23 |
24 | if (value instanceof Date) lastValidDate.current = value
25 |
26 | const editDisplayValue =
27 | value instanceof Date
28 | ? showTime
29 | ? value.toISOString()
30 | : value.toDateString()
31 | : (value as string)
32 | const displayValue = showTime
33 | ? (nodeData.value as Date).toLocaleString()
34 | : (nodeData.value as Date).toLocaleDateString()
35 |
36 | return isEditing ? (
37 | >}
43 | handleEdit={() => {
44 | const newDate = new Date(value as string)
45 | try {
46 | // Check if date is valid by trying to convert to ISO
47 | newDate.toISOString()
48 | handleEdit(newDate)
49 | } catch {
50 | handleEdit(lastValidDate.current)
51 | onError({ code: 'UPDATE_ERROR', message: 'Invalid Date' }, value)
52 | }
53 | }}
54 | />
55 | ) : (
56 |
63 | )
64 | }
65 |
--------------------------------------------------------------------------------
/custom-component-library/components/DateObject/definition.ts:
--------------------------------------------------------------------------------
1 | import { DateObjectCustomComponent, DateObjectProps } from './component'
2 | import { type CustomNodeDefinition } from '@json-edit-react'
3 |
4 | export const DateObjectDefinition: CustomNodeDefinition = {
5 | condition: (nodeData) => nodeData.value instanceof Date,
6 | element: DateObjectCustomComponent,
7 | showEditTools: true,
8 | showOnEdit: true,
9 | name: 'Date Object', // shown in the Type selector menu
10 | showInTypesSelector: true,
11 | defaultValue: new Date(),
12 | renderCollectionAsValue: true,
13 | // IMPORTANT: This component can't be used in conjunction with a ISO string
14 | // matcher (such as the DatePicker in this repo) -- because JSON.stringify
15 | // automatically serializes Date objects to ISO Strings, there's no way to
16 | // distinguish between them when re-parsing back to object.
17 | // There's also no point in providing a stringifyReplacer, as the
18 | // auto-serialisation gets done before passing to the string replacer function
19 | parseReviver: (value) =>
20 | typeof value === 'string' &&
21 | /^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?(?:Z|[+-]\d{2}:?\d{2})?)?$/.test(value)
22 | ? new Date(value)
23 | : value,
24 | }
25 |
--------------------------------------------------------------------------------
/custom-component-library/components/DateObject/index.ts:
--------------------------------------------------------------------------------
1 | export * from './definition'
2 |
--------------------------------------------------------------------------------
/custom-component-library/components/DatePicker/Button.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | // Define the props interface for the Button component
4 | interface ButtonProps {
5 | color?: string
6 | textColor?: string
7 | text?: string
8 | onClick?: () => void
9 | }
10 |
11 | export const Button: React.FC = ({
12 | color = 'rgb(49, 130, 206)',
13 | textColor = 'white',
14 | text = 'Button',
15 | onClick = () => {},
16 | }) => {
17 | const buttonBaseStyles: React.CSSProperties = {
18 | backgroundColor: color,
19 | color: textColor,
20 | }
21 |
22 | return (
23 |
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/custom-component-library/components/DatePicker/component.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * An example Custom Component:
3 | * https://github.com/CarlosNZ/json-edit-react#custom-nodes
4 | *
5 | * A date/time picker which can be configure to show (using the
6 | * CustomNodeDefinitions at the bottom of this file) when an ISO date/time
7 | * string is present in the JSON data, and present a Date picker interface
8 | * rather than requiring the user to edit the ISO string directly.
9 | */
10 |
11 | import React from 'react'
12 | import DatePicker from 'react-datepicker'
13 | import { Button } from './Button'
14 | import { CustomNodeProps } from '@json-edit-react'
15 |
16 | // Styles
17 | import 'react-datepicker/dist/react-datepicker.css'
18 | import './style.css'
19 |
20 | export interface DatePickerCustomProps {
21 | dateFormat?: string
22 | dateTimeFormat?: string
23 | showTime?: boolean
24 | }
25 |
26 | export const DateTimePicker: React.FC> = ({
27 | value,
28 | setValue,
29 | handleEdit,
30 | handleCancel,
31 | handleKeyPress,
32 | isEditing,
33 | setIsEditing,
34 | getStyles,
35 | nodeData,
36 | customNodeProps,
37 | }) => {
38 | const {
39 | dateFormat = 'MMM d, yyyy',
40 | dateTimeFormat = 'MMM d, yyyy h:mm aa',
41 | showTime = true,
42 | } = customNodeProps ?? {}
43 |
44 | const date = new Date(value as string)
45 |
46 | const textColour = getStyles('container', nodeData).backgroundColor
47 | const okColour = getStyles('iconOk', nodeData).color
48 | const cancelColour = getStyles('iconCancel', nodeData).color
49 | const stringStyle = getStyles('string', nodeData)
50 |
51 | return isEditing ? (
52 | // Picker only shows up when "editing". Due to the `showOnView: false` in
53 | // the definition below, this component will not show at all when viewing
54 | // (and so will show raw ISO strings). However, we've defined an alternative
55 | // here too, when showOnView == true, in which case the date/time string is
56 | // shown as a localised date/time.
57 | date && setValue(date.toISOString())}
66 | open={true}
67 | onKeyDown={handleKeyPress}
68 | >
69 |
70 | {/* These buttons are not really necessary -- you can either use the
71 | standard Ok/Cancel icons, or keyboard Enter/Esc, but shown for demo
72 | purposes */}
73 |
74 |
75 |