├── .gitignore ├── DOCS.md ├── LICENSE ├── README.md ├── components.json ├── eslint.config.js ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── src ├── App.tsx ├── assets │ ├── GlobaModeExample.png │ ├── GlobalIndividiualModeExample.png │ ├── IndividualModeExample.png │ ├── InlineModeExample.png │ ├── collapsed.png │ └── expanded.png ├── components │ ├── JsonEditor │ │ ├── defaultElements │ │ │ ├── defaultBooleanInput.tsx │ │ │ ├── defaultDateInput.tsx │ │ │ ├── defaultNumberInput.tsx │ │ │ ├── defaultRadioInput.tsx │ │ │ ├── defaultSelectInput.tsx │ │ │ ├── defaultTextAreaInput.tsx │ │ │ ├── defaultTextInput.tsx │ │ │ └── defaultValueElement.tsx │ │ ├── inlineElements │ │ │ ├── inlineCancelButton.tsx │ │ │ ├── inlineEditButton.tsx │ │ │ └── resetButton.tsx │ │ ├── jsonEditor.css │ │ ├── jsonEditor.tsx │ │ └── renderElements │ │ │ ├── renderArray.tsx │ │ │ ├── renderObject.tsx │ │ │ └── renderValue.tsx │ ├── test.tsx │ └── ui │ │ ├── button.tsx │ │ ├── calendar.tsx │ │ ├── datePicker.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── popover.tsx │ │ ├── radio-group.tsx │ │ ├── select.tsx │ │ ├── tabs.tsx │ │ └── textarea.tsx ├── constants │ └── constants.ts ├── functions │ └── functions.ts ├── index.css ├── lib │ └── utils.ts ├── main.tsx ├── temp.ts ├── types │ └── JsonEditor.types.ts ├── utils │ └── regexTrie.ts └── vite-env.d.ts ├── tailwind.config.js ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.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 | -------------------------------------------------------------------------------- /DOCS.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | Welcome to the documentation for the React JSON Editor Library. This guide provides detailed information on how to effectively integrate and utilize the library in your React applications. You will find information on installation, configuration, available props, and usage examples to help you get started quickly and easily. 4 | 5 | ## Installation 6 | 7 | To install the library, use npm or yarn: 8 | 9 | ```bash 10 | npm install react-json-editor-alt 11 | ``` 12 | 13 | **or** 14 | 15 | ```bash 16 | yarn add react-json-editor-alt 17 | ``` 18 | 19 | ## Basic Usage 20 | 21 | Import the `JsonEditor` component into your desired React component file: 22 | 23 | ```jsx 24 | import {JsonEditor} from 'react-json-editor-alt'; 25 | ``` 26 | 27 | ### Step 3.1: Basic Usage 28 | 29 | ```jsx 30 | import { useState } from 'react'; 31 | import {JsonEditor} from 'react-json-editor-alt'; 32 | 33 | const App = () => { 34 | const [jsonData, setJsonData] = useState({ 35 | name: "John Doe", 36 | age: 30, 37 | active: true 38 | }); 39 | 40 | const handleChange = (props) => { 41 | console.log(props.updatedKeys) 42 | }; 43 | 44 | return ( 45 |
48 | 52 |
53 | ); 54 | }; 55 | 56 | export default App; 57 | ``` 58 | 59 | ### Step 3.2: Advanced Usage 60 | 61 | ```jsx 62 | import { useState } from 'react'; 63 | import {JsonEditor} from 'react-json-editor-alt'; 64 | 65 | const App = () => { 66 | const [jsonData, setJsonData] = useState({ 67 | name: 'John Doe', 68 | id : "DOZJHAH12", 69 | age: 30, 70 | isActive: true, 71 | bio : "Sample bio for john doe", 72 | gender: "male", 73 | contact: { 74 | email : "test@gmail.com", 75 | country : "USA" 76 | } 77 | }); 78 | 79 | const handleChange = (props) => { 80 | console.log(props) 81 | }; 82 | 83 | const handleSubmit = (props) => { 84 | setJsonData(props.updatedJson); 85 | } 86 | 87 | return ( 88 |
91 |

My JSON Editor

92 | 137 |
138 | ); 139 | }; 140 | 141 | export default App; 142 | ``` 143 | 144 | ## JSON Editor Props 145 | 146 | ### `1.json` (object, required) 147 | 148 | The JSON data or JavaScript object to be rendered and edited. The `json` prop must be passed and cannot be `null`, as the editor's primary function is to modify and display this data. 149 | 150 | ```jsx 151 | 159 | ``` 160 | 161 | ### `2.onChange` (function, optional) 162 | 163 | The `onChange` callback function is triggered when a user modifies an editable field in the JSON editor. 164 | 165 | ### `3.onSubmit` (function, optional) 166 | 167 | The `onSubmit` Callback function is triggered when the user submits the changes made in the editor. 168 | 169 | #### onChange|onSubmit Props Types 170 | 171 | | Property | Type | Description | | 172 | | ------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | --- | 173 | | `initialJson` | `Record` | The JSON object representing the initial state before any changes were made. | 174 | | `updatedJson` | `Record` | The JSON object reflecting the state after changes have been applied, including all updates. | 175 | | `updatedKeys` | `DiffKeyValues` | An object mapping the keys that were modified, with each key containing its `initial` and `updated` values. | 176 | | `editorMode` | `EditorMode` | Indicates the current editing mode, which can be one of `global`, `individual`, `global-individual`, or `inline`. | 177 | | `submitType` | `Exclude` | Received only in the `onSubmit` callback handler. Specifies the type of submission, which can be `global`, `individual`, or `inline`. | | 178 | 179 | The submitType prop is helpful in cases where you need to perform actions based on the type of submission. For instance, you might want to switch the editor to read-only mode after a global submission, but not when the user submits an individual field. 180 | 181 | ### `4.isExpanded` (boolean, optional) 182 | 183 | Determines whether the editor should be expanded or collapsed by default. 184 | 185 | - `true`: The editor will show all nested keys expanded upon loading. 186 | - `false`: The editor will start in a collapsed state, hiding nested keys until they are explicitly expanded by the user. 187 | 188 | **Default**: `false` 189 | 190 | The optimal approach is to create a state that allows the user to expand or collapse the nested fields in the JSON editor. 191 | 192 | **For example** 193 | 194 | ```jsx 195 | function IsExpandedExample() { 196 | const [isExpanded, setIsExpanded] = useState(false); 197 | 198 | return ( 199 | <> 200 |
201 | 204 |
205 | 209 | 210 | ); 211 | } 212 | ``` 213 | **Collapsed Mode** 214 | 215 | ![Collapsed](/src/assets/collapsed.png) 216 | 217 | **Expanded Mode** 218 | ![Expanded](/src/assets/expanded.png) 219 | 220 | ### `5.className` (string, optional) 221 | 222 | Allows the user to apply a custom CSS class to the editor component for styling purposes. 223 | 224 | 225 | ### `6.styles` (object, optional) 226 | 227 | Accepts an object of inline styles that will be applied directly to the editor component. 228 | 229 | ### `7.editingConfig` (**Important**) 230 | 231 | --- 232 | 233 | The `editingConfig` prop is the core configuration for defining how the JSON Editor behaves in terms of user interaction, field editing, and rendering. This configuration allows you to control which fields are editable, how they are displayed, and which validation rules apply. 234 | 235 | #### Example Usage 236 | 237 | **Inline Mode** 238 | ```jsx 239 | 250 | ``` 251 | 252 | **Other Modes** 253 | 254 | ```jsx 255 | const [isEditing, setIsEditing] = useState(false); 256 | 257 | 269 | ``` 270 | 271 | 272 | #### `7.1 editingMode` (string, optional) 273 | 274 | Defines how the fields are edited within the JSON Editor. The supported modes are: 275 | - `inline`: Directly edit individual fields by clicking the edit icon next to them. 276 | ![Inline Mode Example](/src/assets/InlineModeExample.png) 277 | 278 | 279 | - `global`: Enter a global editing state, where all fields are editable simultaneously, with a global submit button. 280 | ![Global Mode Example](/src/assets//GlobaModeExample.png) 281 | 282 | 283 | - `individual`: All fields are editable simultaneously but each field has its own submit button for individual updates. 284 | - ![Individual Mode example](/src/assets/IndividualModeExample.png) 285 | 286 | 287 | - `global-individual`: A hybrid mode where both global and individual submissions are allowed. 288 | ![Global-individual mode example](/src/assets/GlobalIndividiualModeExample.png) 289 | 290 | **Default**: `inline` 291 | 292 | #### `7.2 isEditing` (boolean, optional) 293 | 294 | Determines whether the editor is in reading or editing mode. This prop is essential when using modes other than `inline` to switch between the two states. 295 | 296 | - `true`: The editor is in editing mode, allowing the user to modify the fields. 297 | - `false`: The editor is in reading mode, preventing any changes to the fields. 298 | 299 | **Note**: In `inline` mode, the `isEditing` prop has no effect. It is only applicable in `global`, `individual`, and `global-individual` modes, where it must be used to control the editability of the entire editor or specific fields. 300 | 301 | **Default**: `false` 302 | 303 | #### `7.3 allFieldsEditable` (boolean, optional) 304 | 305 | Determines whether all fields are editable by default. 306 | 307 | - `true`: All fields are editable unless explicitly mentioned in the `nonEditableFields` object. 308 | - `false`: All fields are non-editable unless explicitly mentioned in the `editableFields` object. 309 | 310 | This setting is useful when specifying exceptions. For instance, if most fields should be editable, but a few should not, set this to `true` and specify the non-editable fields and vice versa. 311 | 312 | **Default**: `true` 313 | 314 | #### Use Case: 315 | 316 | ```jsx 317 | 327 | ``` 328 | Here, all fields are editable in json except `name` and `contact.email`. 329 | 330 | #### `7.4 editableFields` (object, optional) 331 | 332 | A JSON object specifying which fields are editable and the types or validations they should follow. You can define custom fields with their own input types (`string`, `number`, `textarea`, `boolean`, `radio`, `select`) and validations. 333 | 334 | Each key in the `editableFields` object corresponds to a JSON path, and its value specifies the type of input and validations for that field. 335 | 336 | #### Example Editable Fields Configuration: 337 | 338 | ```jsx 339 | const editableFieldsObject = { 340 | gender: { 341 | type: "radio", 342 | options: [ 343 | { key: "male", value: "male" }, 344 | { key: "female", value: "female" }, 345 | { key: "others", value: "others" }, 346 | ], 347 | }, 348 | description: { 349 | type: "textArea", 350 | validations: { 351 | minLength: 1, 352 | maxLength: 100, 353 | }, 354 | }, 355 | "contact.email": { 356 | type: "string", 357 | validations: { 358 | regex: /^[^@]+@[^@]+\.[^@]+$/, 359 | regexValidationMessage: "Please enter a valid email address.", 360 | }, 361 | }, 362 | "contact.address.country": { 363 | type: "select", 364 | options: [ 365 | { key: "India", value: "India" }, 366 | { key: "USA", value: "USA" }, 367 | ], 368 | }, 369 | "education.[].graduationYear": { 370 | type: "number", 371 | validations: { 372 | maxValue: new Date().getFullYear(), 373 | minLength: 4, 374 | maxLength: 4, 375 | validationMessage: `Please enter a valid year. The year can't be greater than ${new Date().getFullYear()}`, 376 | }, 377 | }, 378 | "hobbies.1": { 379 | type: "string", 380 | validations: { 381 | maxLength: 20, 382 | }, 383 | }, 384 | } 385 | ``` 386 | #### Editable Field Types: 387 | 388 | - `string`: Default field type. Renders a text input. 389 | - `number`: Renders a number input. 390 | - `textArea`: Renders a textarea input with optional length validations. 391 | - `boolean`: Renders a toggle between `true`/`false`. 392 | - `radio`: Renders radio button options. 393 | - `select`: Renders a dropdown with options. 394 | 395 | #### Field Validations: 396 | 397 | - `minLength` / `maxLength`: Set limits for the length of the input value. 398 | - `regex`: Apply a regular expression for validation. 399 | - `validationMessage` / `regexValidationMessage`: Display a custom error message when validation fails. 400 | 401 | Please find more details on validation in the [Validation Section](#validations). 402 | 403 | #### `7.5 nonEditableFields` (object, optional) 404 | 405 | A JSON object that specifies which fields are not editable. This is especially useful when `allFieldsEditable` is set to `true`, allowing you to disable specific fields. 406 | 407 | Each key in `nonEditableFields` corresponds to a JSON path, and its value should be `true` to make the field non-editable. 408 | 409 | #### Example Non-Editable Fields: 410 | 411 | ```jsx 412 | const nonEditableFieldsObject = { 413 | name: true, 414 | "contact.phone": true, 415 | }; 416 | ``` 417 | In this example `name` and `contact.phone` are not editable. 418 | 419 | Note: If a field is present in both editableFields and nonEditableFields, the non-editable field takes precedence and the field becomes non-editable. 420 | 421 | #### 💡 Special JSON Paths: Array Field Targeting 422 | 423 | When working with arrays in your JSON structure, it can be cumbersome to manually specify validation rules or field types for every element in the array. To simplify this, you can use a special syntax in the `editableFields` and `nonEditableFields` paths by including `[]` in place of array indices. 424 | 425 | #### Usage of `[]` for Array Fields 426 | 427 | If you have an array of objects and want to apply a validation or field type to the same key in every object in the array, you can target all elements at once by using `[]` in the path. 428 | 429 | For example: 430 | 431 | ```json 432 | { 433 | "education": [ 434 | { "graduationYear": 2015 }, 435 | { "graduationYear": 2018 }, 436 | { "graduationYear": 2020 } 437 | ] 438 | } 439 | ``` 440 | 441 | You can target the `graduationYear` field across all objects in the education array by using the path `education.[].graduationYear` instead of writing it out for every index (`education.0.graduationYear`, `education.1.graduationYear`, etc.). 442 | 443 | ```jsx 444 | const editableFieldObject = { 445 | "education.[].graduationYear": { 446 | "type": "number", 447 | "validations": { 448 | "minValue": 1900, 449 | "maxValue": new Date().getFullYear(), 450 | "validationMessage": "Please enter a valid graduation year." 451 | } 452 | } 453 | } 454 | ``` 455 | 456 | This ensures that the `graduationYear` field in every object within the education array follows the same validation rules and uses the same input type (number). 457 | 458 | #### More Specific Paths Take Precedence 459 | If you want to apply a specific rule or field type to one particular item in the array, you can do so by specifying the index of the element. More specific paths take precedence over general paths using []. 460 | 461 | **For example:** 462 | 463 | ```jsx 464 | { 465 | "education.[].graduationYear": { 466 | "type": "number", 467 | "validations": { 468 | "minValue": 1900, 469 | "maxValue": new Date().getFullYear(), 470 | "validationMessage": "Please enter a valid graduation year." 471 | } 472 | }, 473 | "education.1.graduationYear": { 474 | "type": "string", 475 | "validations": { 476 | "maxLength": 4, 477 | "validationMessage": "Graduation year must be a string of 4 characters." 478 | } 479 | } 480 | } 481 | ``` 482 | 483 | All elements in the education array will have the graduationYear field as a number with a minimum and maximum value, except for `education[1]`. 484 | For `education[1]`, the graduationYear field will be treated as a string with a maxLength validation of 4 characters. 485 | 486 | This feature helps keep the configuration concise and avoids redundant definitions, while still allowing fine-tuned control over individual array elements when needed. 487 | 488 | #### `7.6 debouncing` (boolean, optional) 489 | 490 | Controls whether input changes are debounced. Debouncing is useful for performance optimization, particularly when handling continuous input changes (such as typing). 491 | 492 | - `true`: Input changes are debounced, reducing the number of times the `onChange` callback is fired. 493 | - `false`: Input changes are processed instantly, and validation messages (if any) are shown immediately. 494 | 495 | **Note**: Setting `debouncing` to true can significantly improve performance when working with large JSON objects. However, for smaller JSONs, the effect may be negligible. However, with debouncing set to true, validation messages will be delayed by 300ms after the user stops typing, as immediate validation is deferred to optimize performance. 496 | 497 | **Default**: `false` 498 | 499 | 500 | #### `7.7 enableTypeBasedRendering` (boolean, optional) 501 | 502 | Enables automatic rendering of input fields based on the type of the JSON value. This means you don't have to explicitly define the field type in the `editableFields` object for basic types like `boolean` and `number`. 503 | 504 | For example: 505 | - A boolean field (`isAdult: true`) will be rendered as a true/false toggle. 506 | - A number field (`age: 23`) will be rendered as a number input. 507 | 508 | If set to `false`, the editor will default to rendering all fields as text inputs unless explicitly defined in `editableFields`. 509 | 510 | **Default**: `true` 511 | 512 | --- 513 | 514 | ### `8.globalSubmitButtonConfigs` 515 | 516 | A configuration Object to customise global submit button in `global` and `global-individual` editing mode. 517 | 518 | #### `8.1 variant` (string, optional) 519 | 520 | Defines the visual style of the button. You can choose from the following options: 521 | 522 | - `"secondary"`: A standard secondary button style. 523 | - `"outline"`: A button with an outlined style. 524 | - `"ghost"`: A transparent button with minimal styling. 525 | - `"link"`: A button that resembles a hyperlink. 526 | - `"destructive"`: A button styled to indicate a destructive action (e.g., delete). 527 | 528 | #### `8.2 className` (string, optional) 529 | 530 | A custom CSS class that can be added to the button for styling purposes. This allows you to apply additional styles or override default styles to match your application's design. 531 | 532 | #### `8.3 buttonText` (string, optional) 533 | 534 | The text that will be displayed on the button. This prop provides a simple way to set the button's label to communicate its action clearly to users. 535 | 536 | #### `8.4 children` (React.ReactNode, optional) 537 | 538 | Enables the inclusion of nested React components or elements within the button. This allows for more complex button content, such as icons or additional text elements, providing greater flexibility in design. 539 | 540 | ### Example 541 | 542 | ```jsx 543 | 551 | ``` 552 | 553 | ## Validations 554 | 555 | The `validations` object is applicable to the following field types: 556 | 557 | - `string` 558 | - `number` 559 | - `textarea` 560 | 561 | The validation can be defined using either basic length-based properties or regex-based validation. Here’s how they work: 562 | 563 | #### 1.Basic Length Validation: 564 | 565 | - `minLength`: Specifies the minimum number of characters allowed. 566 | - `maxLength`: Specifies the maximum number of characters allowed. 567 | - `validationMessage`: A custom message to be shown to the user if the validation fails. If no custom message is provided, a default message will be displayed. 568 | 569 | **Example:** 570 | 571 | ```json 572 | "description": { 573 | "type": "textArea", 574 | "validations": { 575 | "minLength": 10, 576 | "maxLength": 100, 577 | "validationMessage": "Description must be between 10 and 100 characters." 578 | } 579 | } 580 | ``` 581 | 582 | #### 2.Regex Validation: 583 | 584 | - `regex`: Allows you to define a custom regular expression for validation. This is useful for more complex validations like email formats or phone numbers. 585 | - `regexValidationMessage`: A custom message to be shown to the user if the regex validation fails. Like `validationMessage`, if no custom message is provided, a default message will be shown. 586 | 587 | #### Example: 588 | 589 | ```json 590 | "contact.email": { 591 | "type": "string", 592 | "validations": { 593 | "regex": /^[^@]+@[^@]+\.[^@]+$/, 594 | "regexValidationMessage": "Please enter a valid email address." 595 | } 596 | } 597 | ``` 598 | 599 | **Note**: When using regex validation, it can cover most validation needs, so the length properties like minLength or maxLength aren't needed, as regex itself can handle these checks. 600 | 601 | #### 3.Number Field-Specific Validation 602 | 603 | For `number` type fields, additional properties can be used to set minimum and maximum values: 604 | 605 | - `minValue`: Specifies the minimum value allowed for the field. 606 | - `maxValue`: Specifies the maximum value allowed for the field. 607 | 608 | #### Example: 609 | 610 | ```json 611 | "education.[].graduationYear": { 612 | "type": "number", 613 | "validations": { 614 | "minValue": 1900, 615 | "maxValue": new Date().getFullYear(), 616 | "validationMessage": "Please enter a valid graduation year between 1900 and the current year." 617 | } 618 | } 619 | ``` 620 | ## Examples 621 | Examples are the most effective way to understand a library’s functionality. Visit [examples](https://himalaya0035.github.io/react-json-editor-alt/) to see what's available. Currently, only a few examples are provided, but we plan to add more soon. In the meantime, please refer to this official documentation. Thank you for your patience! 622 | 623 | ## Community and Support 624 | 625 | We hope this documentation has helped you get the most out of the React JSON Editor. If you have any questions, encounter issues, or want to request features, feel free to open an issue or contribute directly via GitHub. We value your feedback and are always working to improve the library. 626 | 627 | Stay tuned for future updates, and thank you for using the React JSON Editor! 628 | 629 | If you find this library useful, consider giving it a **star** on [GitHub](https://github.com/himalaya0035/react-json-editor-alt). 630 | 631 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Himalaya Gupta 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # REACT JSON EDITOR 2 | 3 | The **React JSON Editor** is a flexible and easy-to-use library for rendering and editing JavaScript objects or JSON data in React applications. It lets developers editable and non-editable fields, add validation, and display inputs like select, boolean, radio, textarea, etc., based on field types and editing configuarations ensuring a smooth user experience. 4 | 5 | ![GitHub license](https://img.shields.io/github/license/himalaya0035/chrome-extension-boilerplate-react-vite-typescript) 6 | ![React](https://img.shields.io/badge/react-18.x-blue) 7 | ![TypeScript](https://img.shields.io/badge/typescript-5.x-blue) 8 | ![Tailwind CSS](https://img.shields.io/badge/tailwindcss-3.x-blue) 9 | ![Vite](https://img.shields.io/badge/vite-5.x-blue) 10 | 11 | 12 | ## 🌐 Links 13 | 14 | - [📂 **GitHub Repository**](https://github.com/himalaya0035/react-json-editor-alt) 15 | Explore the codebase, raise issues, or contribute to the project. 16 | 17 | - [📄 **Documentation**](https://github.com/himalaya0035/react-json-editor-alt/blob/main/DOCS.md) 18 | Access the complete documentation for installation, usage, and more details. 19 | 20 | - [🖥️ **Examples/Demo**](https://himalaya0035.github.io/react-json-editor-alt/) 21 | Try out the live demo and examples to see the editor in action. 22 | 23 | ## Screenshots 24 | ![Global Mode Example](https://github.com/himalaya0035/react-json-editor-alt/blob/main/src/assets/GlobaModeExample.png) 25 | 26 | ![Inline Mode Example](https://github.com/himalaya0035/react-json-editor-alt/blob/main/src/assets/InlineModeExample.png) 27 | 28 | ## Why Use This Library? 29 | 30 | - ✏️ **Dynamic JSON Editing**: Define which fields are editable or non-editable at a granular level. 31 | - ⚙️ **Flexible Field Rendering**: Configure editable fields to be displayed as dropdowns, radio buttons, boolean toggles, datepickers, text areas, and other input types. 32 | - 🔧 **Advanced Validation**: Support for length-based, regex-based, and number-specific validations (min/max values) to ensure data accuracy. 33 | - 🔄 **Multiple Editing Modes**: Seamlessly switch between `inline`, `global`, `individual`, or `global-individual` editing modes, providing a tailored editing experience based on the use case. 34 | - 🧩 **Type-Based Rendering**: Automatically render input types for common JSON values such as booleans and numbers without explicit configuration. 35 | - ⚡ **Performance Optimizations**: Features like debouncing help ensure smooth and responsive interactions, even for large JSON structures. 36 | 37 | Whether you're building complex forms, handling flexible data, or need a customizable JSON editor, this library offers the tools to do it efficiently and easily. 38 | 39 | ## Quick Start Guide 40 | 41 | Get started with the **React JSON Editor** in just a few steps! For detailed documentation, refer to the in depth guide: [DOCS](https://github.com/himalaya0035/react-json-editor-alt/blob/main/DOCS.md). 42 | 43 | ### Step 1: Installation 44 | 45 | To install the library, use npm or yarn: 46 | 47 | ```bash 48 | npm install react-json-editor-alt 49 | ``` 50 | 51 | **or** 52 | 53 | ```bash 54 | yarn add react-json-editor-alt 55 | ``` 56 | 57 | ### Step 2: Import the Component 58 | Import the `JsonEditor` component into your desired React component file: 59 | 60 | ```jsx 61 | import {JsonEditor} from 'react-json-editor-alt'; 62 | ``` 63 | 64 | ### Step 3.1: Basic Usage 65 | 66 | ```jsx 67 | import { useState } from 'react'; 68 | import {JsonEditor} from 'react-json-editor-alt'; 69 | 70 | const App = () => { 71 | const [jsonData, setJsonData] = useState({ 72 | name: "John Doe", 73 | age: 30, 74 | active: true 75 | }); 76 | 77 | const handleChange = (props) => { 78 | console.log(props.updatedKeys) 79 | }; 80 | 81 | return ( 82 |
85 | 89 |
90 | ); 91 | }; 92 | 93 | export default App; 94 | ``` 95 | 96 | ### Step 3.2: Advanced Usage 97 | 98 | ```jsx 99 | import { useState } from 'react'; 100 | import {JsonEditor} from 'react-json-editor-alt'; 101 | 102 | const App = () => { 103 | const [jsonData, setJsonData] = useState({ 104 | name: 'John Doe', 105 | id : "DOZJHAH12", 106 | age: 30, 107 | isActive: true, 108 | bio : "Sample bio for john doe", 109 | gender: "male", 110 | contact: { 111 | email : "test@gmail.com", 112 | country : "USA" 113 | } 114 | }); 115 | 116 | const handleChange = (props) => { 117 | console.log(props) 118 | }; 119 | 120 | const handleSubmit = (props) => { 121 | setJsonData(props.updatedJson); 122 | } 123 | 124 | return ( 125 |
128 |

My JSON Editor

129 | 174 |
175 | ); 176 | }; 177 | 178 | export default App; 179 | ``` 180 | 181 | ### Step 4: Customizing the Editor 182 | You can customize the editor by adjusting the `editingConfig` prop. This allows you to define editable and non-editable fields, validation rules, and more. Refer to the [editingConfig prop docs](https://github.com/himalaya0035/react-json-editor-alt/blob/main/DOCS.md#editingConfig) for details. 183 | 184 | ## Examples/Demo 185 | 186 | Examples are the most effective way to understand a library’s functionality. Visit [examples](https://himalaya0035.github.io/react-json-editor-alt/) to see what's available. Currently, only a few examples are provided, but we plan to add more soon. In the meantime, please refer to the [official documentation](https://github.com/himalaya0035/react-json-editor-alt/blob/main/DOCS.md) for examples and usage guidelines to help you get started. Thank you for your patience! 187 | 188 | ## API Reference 189 | 190 | This section provides a brief overview of the props used in the **React JSON Editor**. Each prop's type, purpose, and whether it is required or optional is outlined below. 191 | 192 | ### JsonEditor Props 193 | 194 | | Prop Name | Type | Required | Description | 195 | |-------------------|----------------|----------|-------------------------------------------------------------------------------------------------------| 196 | | `json` | `object` | Yes | The JSON data or JavaScript object to be rendered and edited. | 197 | | `onChange` | `function` | No | Callback function triggered when a user modifies an editable field in the JSON editor. | 198 | | `onSubmit` | `function` | No | Callback function called when the user submits the changes made in the editor. | 199 | | `editingConfig` | `object` | No | Configuration Object that controls editing behavior, specifying editable and non-editable fields along with their validations. | 200 | | `isExpanded` | `boolean` | No | Controls whether the editor is expanded or collapsed. | 201 | | `className` | `string` | No | Custom CSS class for styling the editor component. | 202 | | `styles` | `object` | No | Inline styles to be applied to the editor component. | 203 | | `globalSubmitButtonConfigs` | `object` | No | Configuration Object to customise global submit button in `global` and `global-individual` editing mode. | 204 | 205 | ### onChange|onSubmit Props Types 206 | 207 | | Property | Type | Description || 208 | |----------------|--------------------|----------|-----------------------------------------------------------------------------------------------------------| 209 | | `initialJson` | `Record` | The JSON object representing the initial state before any changes were made. | 210 | | `updatedJson` | `Record` | The JSON object reflecting the state after changes have been applied, including all updates. | 211 | | `updatedKeys` | `DiffKeyValues` | An object mapping the keys that were modified, with each key containing its `initial` and `updated` values. | 212 | | `editorMode` | `EditorMode` | Indicates the current editing mode, which can be one of `global`, `individual`, `global-individual`, or `inline`. | 213 | | `validations` | `Record` | Available only in the `onChange` callback handler, this object contains all current validation errors in the editor. | 214 | | `submitType` | `Exclude` | Available only in the `onSubmit` callback handler. Specifies the type of submission, which can be `global`, `individual`, or `inline`. | | 215 | 216 | 217 | ### Editing Config Props 218 | 219 | | Prop Name | Type | Required | Description | 220 | |--------------------------|----------------|----------|-------------------------------------------------------------------------------------------------------| 221 | | `isEditing` | `boolean` | No | Determines whether the editor is in editing mode. Not required in `inline` editing mode. | 222 | | `editingMode` | `string` | No | Defines how fields are edited (e.g., `inline`, `global`, `individual`, or `global-individual`). | 223 | | `allFieldsEditable` | `boolean` | No | Indicates whether all fields are editable by default. | 224 | | `editableFields` | `object` | No | Specifies which fields are editable along with their types and validations. | 225 | | `nonEditableFields` | `object` | No | Specifies which fields are non-editable, overriding the editableFields settings. | 226 | | `debouncing` | `boolean` | No | Controls if input changes are debounced for performance optimization. | 227 | | `enableTypeBasedRendering`| `boolean` | No | Enables automatic rendering of input fields based on the type of the JSON value. | 228 | 229 | ### Global Submit Button Configs 230 | 231 | A configuration Object to customise global submit button in `global` and `global-individual` editing mode. 232 | 233 | | Property | Type | Required | Description | 234 | |----------------|-------------------|----------|---------------------------------------------------------------------------------------------------| 235 | | `variant` | `string` | No | Specifies the variant style of the button. Options include `"secondary"`, `"outline"`, `"ghost"`, `"link"`, and `"destructive"`. | 236 | | `className` | `string` | No | Custom CSS class for styling the button. Allows for additional styling options beyond default styles. | 237 | | `buttonText` | `string` | No | Text to be displayed on the button. This provides a straightforward way to set the button label. | 238 | | `children` | `React.ReactNode` | No | Allows for the inclusion of nested React components or elements within the button, enabling complex content. | 239 | 240 | ## Planned Features 241 | 242 | We are currently working on some exciting features for our next release. To make our library even better, we’d love to hear your thoughts! Please specify which features you would like to see implemented next. Your feedback is invaluable in shaping the future of this library. 243 | 244 | Feel free to request features by creating an issue in our [GitHub repository](https://github.com/himalaya0035/react-json-editor-alt/issues) or participating in the discussions. 245 | 246 | **Note**: The React JSON Editor works best in projects where Tailwind CSS is already installed. In projects without Tailwind, there may be instances where global styles of other components could be affected. We are addressing this issue for future releases. 247 | 248 | ## Contributing 249 | 250 | Contributions are welcome! If you encounter any issues or have ideas for improvements, feel free to open an issue or submit a pull request. 251 | 252 | To get started: 253 | 254 | 1. Fork the repository and clone it locally. 255 | 2. Install dependencies: `npm install` 256 | 3. Create a new branch: `git checkout -b feature-name` 257 | 4. Make your changes and commit: `git commit -m 'Add some feature'` 258 | 5. Push to your branch and open a pull request. 259 | 260 | ## License 261 | 262 | This project is licensed under the [MIT License](LICENSE). 263 | 264 | ## Support 265 | 266 | ⭐️ If you find this library useful, consider giving it a star on [GitHub](https://github.com/himalaya0035/react-json-editor-alt). 267 | 268 | --- 269 | 270 | Happy coding! If you have any questions or need further assistance, please don't hesitate to reach out. 271 | 272 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "src/index.css", 9 | "baseColor": "slate", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import reactHooks from 'eslint-plugin-react-hooks' 4 | import reactRefresh from 'eslint-plugin-react-refresh' 5 | import tseslint from 'typescript-eslint' 6 | 7 | export default tseslint.config( 8 | { ignores: ['dist'] }, 9 | { 10 | extends: [js.configs.recommended, ...tseslint.configs.recommended], 11 | files: ['**/*.{ts,tsx}'], 12 | languageOptions: { 13 | ecmaVersion: 2020, 14 | globals: globals.browser, 15 | }, 16 | plugins: { 17 | 'react-hooks': reactHooks, 18 | 'react-refresh': reactRefresh, 19 | }, 20 | rules: { 21 | ...reactHooks.configs.recommended.rules, 22 | 'react-refresh/only-export-components': [ 23 | 'warn', 24 | { allowConstantExport: true }, 25 | ], 26 | }, 27 | }, 28 | ) 29 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rje", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc -b && vite build", 9 | "lint": "eslint .", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@radix-ui/react-icons": "^1.3.0", 14 | "@radix-ui/react-label": "^2.1.0", 15 | "@radix-ui/react-popover": "^1.1.1", 16 | "@radix-ui/react-radio-group": "^1.2.0", 17 | "@radix-ui/react-select": "^2.1.1", 18 | "@radix-ui/react-slot": "^1.1.0", 19 | "@radix-ui/react-tabs": "^1.1.0", 20 | "class-variance-authority": "^0.7.0", 21 | "clsx": "^2.1.1", 22 | "date-fns": "^3.6.0", 23 | "lucide-react": "^0.428.0", 24 | "react": "^18.3.1", 25 | "react-day-picker": "^8.10.1", 26 | "react-dom": "^18.3.1", 27 | "tailwind-merge": "^2.5.2", 28 | "tailwindcss-animate": "^1.0.7" 29 | }, 30 | "devDependencies": { 31 | "@eslint/js": "^9.9.0", 32 | "@types/node": "^22.3.0", 33 | "@types/react": "^18.3.3", 34 | "@types/react-dom": "^18.3.0", 35 | "@vitejs/plugin-react": "^4.3.1", 36 | "autoprefixer": "^10.4.20", 37 | "eslint": "^9.9.0", 38 | "eslint-plugin-react-hooks": "^5.1.0-rc.0", 39 | "eslint-plugin-react-refresh": "^0.4.9", 40 | "globals": "^15.9.0", 41 | "postcss": "^8.4.41", 42 | "tailwindcss": "^3.4.10", 43 | "typescript": "^5.5.3", 44 | "typescript-eslint": "^8.0.1", 45 | "vite": "^5.4.1" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import JsonEditor from "./components/JsonEditor/jsonEditor"; 3 | import { 4 | EditableFielsdObjectType, 5 | NonEditableFieldsObjectType, 6 | onChangePropsType, 7 | OnSubmitPropsType, 8 | } from "./types/JsonEditor.types"; 9 | import { indianStatesOptions } from "./temp"; 10 | import { Button } from "./components/ui/button"; 11 | import { GLOBAL_EDITING_MODE } from "./constants/constants"; 12 | 13 | function App() { 14 | const [isEditing, setIsEditing] = useState(false); 15 | const [isExpanded, setIsExpanded] = useState(false); 16 | 17 | const x: Record = { 18 | name: "Himalaya", 19 | surname: true, 20 | age : 23, 21 | testNumberField: 2292, 22 | testNullField: null, 23 | dob: "03/12/2000", 24 | about: "Hello, I am a software developer", 25 | address: { 26 | city: "Bareilly", 27 | state: "Uttar Pradesh", 28 | key: { 29 | _id: "66c3ab28dc5e92c6ea662626", 30 | name: "Allen Beck", 31 | gender: "male", 32 | company: "HAWKSTER", 33 | email: "allenbeck@hawkster.com", 34 | phone: 283839389, 35 | secondKey: { 36 | _id: "66c3ab28dc5e92c6ea662626", 37 | name: "Allen Beck", 38 | gender: "female", 39 | company: "HAWKSTER", 40 | email: "allenbeck@hawkster.com", 41 | phone: "+1 (866) 599-3761", 42 | }, 43 | }, 44 | }, 45 | sampleData: [ 46 | { 47 | _id: "66c3ab28dc5e92c6ea662626", 48 | name: "Allen Beck", 49 | gender: "male", 50 | company: "HAWKSTER", 51 | email: "allenbeck@hawkster.com", 52 | phone: "+1 (866) 599-3761", 53 | secondKey: { 54 | _id: "66c3ab28dc5e92c6ea662626", 55 | name: "Allen Beck", 56 | gender: "male", 57 | company: "HAWKSTER", 58 | email: "allenbeck@hawkster.com", 59 | phone: "+1 (866) 599-3761", 60 | thirdKey: { 61 | _id: "66c3ab28dc5e92c6ea662626", 62 | name: "Allen Beck", 63 | gender: "male", 64 | company: "HAWKSTER", 65 | email: "allenbeck@hawkster.com", 66 | phone: "+1 (866) 599-3761", 67 | }, 68 | }, 69 | }, 70 | { 71 | _id: "66c3ab28cdadb9ffd1e92675", 72 | name: "Walters Mullen", 73 | gender: "male", 74 | company: "BOILICON", 75 | email: "waltersmullen@boilicon.com", 76 | phone: "+1 (911) 573-2834", 77 | }, 78 | ], 79 | hobbies: ["Movies", "Music"], 80 | }; 81 | 82 | const editbaleFieldsObject: EditableFielsdObjectType = { 83 | dob: { 84 | type: "date", 85 | format: "DD/MM/YYYY", 86 | }, 87 | age : { 88 | type : "number", 89 | validations : { 90 | minValue:1, 91 | maxValue: 50, 92 | } 93 | }, 94 | about: { 95 | type: "textArea", 96 | validations : { 97 | minLength : 1, 98 | } 99 | }, 100 | "address.state": { 101 | type: "select", 102 | options: indianStatesOptions, 103 | }, 104 | "address.key.phone": { 105 | type: "number" 106 | }, 107 | "sampleData.[].email": { 108 | type: "string", 109 | validations: { 110 | regex: /^[^@]+@[^@]+\.[^@]+$/, 111 | regexValidationMessage: "Please enter a valid email address.", 112 | }, 113 | }, 114 | "address.key.gender": { 115 | type: "radio", 116 | options: [ 117 | { key: "male", value: "male" }, 118 | { key: "female", value: "female" }, 119 | { key: "others", value: "others" }, 120 | ], 121 | }, 122 | "address.key.secondKey.gender": { 123 | type: "radio", 124 | options: [ 125 | { key: "male", value: "male" }, 126 | { key: "female", value: "female" }, 127 | { key: "others", value: "others" }, 128 | ], 129 | }, 130 | "sampleData.[].secondKey.gender": { 131 | type: "radio", 132 | options: [ 133 | { key: "male", value: "male" }, 134 | { key: "female", value: "female" }, 135 | { key: "other", value: "other" }, 136 | ], 137 | }, 138 | "sampleData.[].gender" : { 139 | type : "radio", 140 | options: [ 141 | { key: "male", value: "male" }, 142 | { key: "female", value: "female" }, 143 | { key: "other", value: "other" }, 144 | ], 145 | }, 146 | "sampleData.[].name" : { 147 | type : "textArea", 148 | validations : { 149 | maxLength : 12 150 | } 151 | }, 152 | "hobbies.[]" : { 153 | type: "string", 154 | validations: { 155 | maxLength : 20 156 | } 157 | }, 158 | "hobbies.1" : { 159 | type : "textArea", 160 | } 161 | }; 162 | 163 | const nonEditbaleFieldObject: NonEditableFieldsObjectType = {}; 164 | 165 | const onSubmit = (props : OnSubmitPropsType) => { 166 | console.info(props) 167 | if (props.submitType === GLOBAL_EDITING_MODE){ 168 | setIsEditing(false) 169 | } 170 | } 171 | 172 | const onChange = (props : onChangePropsType) => { 173 | console.info(props) 174 | } 175 | 176 | return ( 177 |
178 |

Json Editor

179 |
180 | 183 | 186 |
187 | 203 |
204 | ); 205 | } 206 | 207 | export default App; 208 | -------------------------------------------------------------------------------- /src/assets/GlobaModeExample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himalaya0035/react-json-editor-alt/b4a0267a334ce9e3eb06db3653d848365da5f5b9/src/assets/GlobaModeExample.png -------------------------------------------------------------------------------- /src/assets/GlobalIndividiualModeExample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himalaya0035/react-json-editor-alt/b4a0267a334ce9e3eb06db3653d848365da5f5b9/src/assets/GlobalIndividiualModeExample.png -------------------------------------------------------------------------------- /src/assets/IndividualModeExample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himalaya0035/react-json-editor-alt/b4a0267a334ce9e3eb06db3653d848365da5f5b9/src/assets/IndividualModeExample.png -------------------------------------------------------------------------------- /src/assets/InlineModeExample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himalaya0035/react-json-editor-alt/b4a0267a334ce9e3eb06db3653d848365da5f5b9/src/assets/InlineModeExample.png -------------------------------------------------------------------------------- /src/assets/collapsed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himalaya0035/react-json-editor-alt/b4a0267a334ce9e3eb06db3653d848365da5f5b9/src/assets/collapsed.png -------------------------------------------------------------------------------- /src/assets/expanded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/himalaya0035/react-json-editor-alt/b4a0267a334ce9e3eb06db3653d848365da5f5b9/src/assets/expanded.png -------------------------------------------------------------------------------- /src/components/JsonEditor/defaultElements/defaultBooleanInput.tsx: -------------------------------------------------------------------------------- 1 | import { Check } from "lucide-react"; 2 | import { GLOBAL_EDITING_MODE, INLINE_EDITING_MODE } from "../../../constants/constants"; 3 | import { DefaultBooleanElementProps } from "../../../types/JsonEditor.types"; 4 | import { Button } from "../../ui/button"; 5 | import { Tabs, TabsList, TabsTrigger } from "../../ui/tabs"; 6 | import InlineCancelButton from "../inlineElements/inlineCancelButton"; 7 | import { useJsonEditorContext } from "../jsonEditor"; 8 | import ResetButton from "../inlineElements/resetButton"; 9 | 10 | function DefaultBooleanInput({ value, readModeValue, path }: DefaultBooleanElementProps) { 11 | const { 12 | editingMode, 13 | handleOnChange, 14 | handleOnSubmit, 15 | setSelectedFieldsForEditing, 16 | } = useJsonEditorContext(); 17 | 18 | const booleanAsString = 19 | value === true ? "true" : value === false ? "false" : ""; 20 | 21 | const handleBooleanInputChange = (selectedValue: string) => { 22 | const stringAsBoolean = selectedValue === "true" ? true : false; 23 | handleOnChange(stringAsBoolean, path); 24 | }; 25 | 26 | const handleBooleanInputSubmit = () => { 27 | handleOnSubmit(value, path); 28 | if (editingMode === INLINE_EDITING_MODE) { 29 | setSelectedFieldsForEditing((prev) => { 30 | return { 31 | ...prev, 32 | [path]: false, 33 | }; 34 | }); 35 | } 36 | }; 37 | 38 | const disabled = readModeValue === value; 39 | 40 | return ( 41 | <> 42 | 46 | 47 | True 48 | False 49 | 50 | 51 | {editingMode !== GLOBAL_EDITING_MODE && ( 52 | 62 | )} 63 | {editingMode === INLINE_EDITING_MODE && } 64 | {(editingMode !== INLINE_EDITING_MODE && !disabled) && } 65 | 66 | ); 67 | } 68 | 69 | export default DefaultBooleanInput; 70 | -------------------------------------------------------------------------------- /src/components/JsonEditor/defaultElements/defaultDateInput.tsx: -------------------------------------------------------------------------------- 1 | import { Check } from "lucide-react"; 2 | import { convertDateIntoPattern, convertPatternIntoDate } from "../../../functions/functions"; 3 | import { DefaultDateElementProps } from "../../../types/JsonEditor.types"; 4 | import { Button } from "../../ui/button"; 5 | import { DatePicker } from "../../ui/datePicker"; 6 | import { useJsonEditorContext } from "../jsonEditor"; 7 | import InlineCancelButton from "../inlineElements/inlineCancelButton"; 8 | import { GLOBAL_EDITING_MODE, INLINE_EDITING_MODE } from "../../../constants/constants"; 9 | import ResetButton from "../inlineElements/resetButton"; 10 | 11 | function DefaultDateInput({value,readModeValue,path,format}: DefaultDateElementProps) { 12 | const { 13 | handleOnChange, 14 | handleOnSubmit, 15 | editingMode, 16 | setSelectedFieldsForEditing, 17 | } = useJsonEditorContext(); 18 | 19 | const dateValue = convertPatternIntoDate(value,format) 20 | if (!dateValue){ 21 | return "Invalid Date" 22 | } 23 | 24 | const handleDateInputChange = (selectedDate : Date | undefined) => { 25 | const dateString = convertDateIntoPattern(selectedDate as Date,format) 26 | if (dateString){ 27 | handleOnChange(dateString,path) 28 | } 29 | }; 30 | 31 | const handleDateInputSubmit = () => { 32 | handleOnSubmit(value, path); 33 | if (editingMode === INLINE_EDITING_MODE) { 34 | setSelectedFieldsForEditing((prev) => { 35 | return { 36 | ...prev, 37 | [path]: false, 38 | }; 39 | }); 40 | } 41 | }; 42 | 43 | let disabled = readModeValue === value; 44 | 45 | return ( 46 | <> 47 | 48 | {editingMode !== GLOBAL_EDITING_MODE && ( 49 | 59 | )} 60 | {editingMode === INLINE_EDITING_MODE && } 61 | {(editingMode !== INLINE_EDITING_MODE && !disabled) && } 62 | 63 | ); 64 | 65 | 66 | } 67 | 68 | export default DefaultDateInput; -------------------------------------------------------------------------------- /src/components/JsonEditor/defaultElements/defaultNumberInput.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from "react"; 2 | import { DefaultNumberElementProps } from "../../../types/JsonEditor.types"; 3 | import { Input } from "../../ui/input"; 4 | import { debounce, validateValue } from "../../../functions/functions"; 5 | import { DEBOUNCE_DELAY, GLOBAL_EDITING_MODE, INLINE_EDITING_MODE } from "../../../constants/constants"; 6 | import { Button } from "../../ui/button"; 7 | import { Check } from "lucide-react"; 8 | import { useJsonEditorContext } from "../jsonEditor"; 9 | import InlineCancelButton from "../inlineElements/inlineCancelButton"; 10 | import ResetButton from "../inlineElements/resetButton"; 11 | 12 | function DefaultNumberInput({ 13 | value, 14 | readModeValue, 15 | path, 16 | fieldValidations 17 | }: DefaultNumberElementProps) { 18 | const [numberInputValue, setNumberInputValue] = useState(value); 19 | const [localValidationError, setLocalValidationError] = useState('') 20 | const { 21 | handleOnChange, 22 | handleOnSubmit, 23 | editingMode, 24 | setSelectedFieldsForEditing, 25 | validations, 26 | setValidations, 27 | debouncing 28 | } = useJsonEditorContext(); 29 | 30 | const handleNumberInputChange = (e: React.ChangeEvent) => { 31 | const value = e.target.value; 32 | let result = null; 33 | if (fieldValidations){ 34 | result = validateValue(value,fieldValidations) 35 | setLocalValidationError(result || '') 36 | } 37 | setNumberInputValue(value); 38 | debouncedOnChange(value,result || ""); 39 | }; 40 | 41 | // Memoize debounced onChange with useCallback, recreating when onChange updates. 42 | // This prevents stale closures and ensures the component uses the latest onChange. 43 | const debouncedOnChange = useCallback( 44 | debounce((value: string,validationMessage? : string) => { 45 | const updatedValidations = { 46 | ...validations, 47 | [path] : validationMessage 48 | } 49 | handleOnChange(Number(value), path,updatedValidations); 50 | if (fieldValidations){ 51 | setValidations(prev => { 52 | return { 53 | ...prev, 54 | [path] : validationMessage 55 | } 56 | }) 57 | } 58 | }, debouncing ? DEBOUNCE_DELAY : 0), 59 | [handleOnChange] 60 | ); 61 | 62 | const handleNumberInputSubmit = () => { 63 | handleOnSubmit(Number(numberInputValue), path); 64 | if (editingMode === INLINE_EDITING_MODE) { 65 | setSelectedFieldsForEditing((prev) => { 66 | return { 67 | ...prev, 68 | [path]: false, 69 | }; 70 | }); 71 | } 72 | }; 73 | 74 | const disabled = readModeValue === Number(numberInputValue); 75 | const validationMessage = localValidationError || validations[path] 76 | 77 | return ( 78 | <> 79 | 84 | {editingMode !== GLOBAL_EDITING_MODE && !validationMessage && ( 85 | 95 | )} 96 | {editingMode === INLINE_EDITING_MODE && } 97 | {(editingMode !== INLINE_EDITING_MODE && !disabled) && { 98 | setNumberInputValue(readModeValue as number) 99 | setLocalValidationError("") 100 | }} />} 101 | {validationMessage} 102 | 103 | ); 104 | } 105 | 106 | export default DefaultNumberInput; 107 | -------------------------------------------------------------------------------- /src/components/JsonEditor/defaultElements/defaultRadioInput.tsx: -------------------------------------------------------------------------------- 1 | import { RadioGroup, RadioGroupItem } from "../../ui/radio-group"; 2 | import { Label } from "../../ui/label"; 3 | import { DefaultRadioElementProps } from "../../../types/JsonEditor.types"; 4 | import { Button } from "../../ui/button"; 5 | import { Check } from "lucide-react"; 6 | import { useJsonEditorContext } from "../jsonEditor"; 7 | import InlineCancelButton from "../inlineElements/inlineCancelButton"; 8 | import { GLOBAL_EDITING_MODE, INLINE_EDITING_MODE } from "../../../constants/constants"; 9 | import ResetButton from "../inlineElements/resetButton"; 10 | 11 | function DefaultRadioInput({ 12 | value, 13 | readModeValue, 14 | path, 15 | options, 16 | }: DefaultRadioElementProps) { 17 | const { 18 | handleOnChange, 19 | handleOnSubmit, 20 | editingMode, 21 | setSelectedFieldsForEditing, 22 | } = useJsonEditorContext(); 23 | 24 | const handleRadioInputChange = (selectedValue: string) => { 25 | handleOnChange(selectedValue, path); 26 | }; 27 | 28 | const handleRadioInputSubmit = () => { 29 | handleOnSubmit(value, path); 30 | if (editingMode === INLINE_EDITING_MODE) { 31 | setSelectedFieldsForEditing((prev) => { 32 | return { 33 | ...prev, 34 | [path]: false, 35 | }; 36 | }); 37 | } 38 | }; 39 | 40 | let disabled = readModeValue === value; 41 | 42 | return ( 43 | <> 44 | 49 | {options.map((option) => ( 50 |
51 | 52 | 53 |
54 | ))} 55 |
56 | {editingMode !== GLOBAL_EDITING_MODE && ( 57 | 67 | )} 68 | {editingMode === INLINE_EDITING_MODE && } 69 | {(editingMode !== INLINE_EDITING_MODE && !disabled) && } 70 | 71 | ); 72 | } 73 | 74 | export default DefaultRadioInput; 75 | -------------------------------------------------------------------------------- /src/components/JsonEditor/defaultElements/defaultSelectInput.tsx: -------------------------------------------------------------------------------- 1 | import { Check } from "lucide-react"; 2 | import { DefaultSelectElementProps } from "../../../types/JsonEditor.types"; 3 | import { Button } from "../../ui/button"; 4 | import { 5 | Select, 6 | SelectContent, 7 | SelectItem, 8 | SelectTrigger, 9 | SelectValue, 10 | } from "../../ui/select"; 11 | import { useJsonEditorContext } from "../jsonEditor"; 12 | import InlineCancelButton from "../inlineElements/inlineCancelButton"; 13 | import { GLOBAL_EDITING_MODE, INLINE_EDITING_MODE } from "../../../constants/constants"; 14 | import ResetButton from "../inlineElements/resetButton"; 15 | 16 | function DefaultSelectInput({ 17 | value, 18 | readModeValue, 19 | path, 20 | options, 21 | }: DefaultSelectElementProps) { 22 | const { 23 | handleOnChange, 24 | handleOnSubmit, 25 | editingMode, 26 | setSelectedFieldsForEditing, 27 | } = useJsonEditorContext(); 28 | 29 | const handleSelectInputChange = (selectedValue: string) => { 30 | handleOnChange(selectedValue, path); 31 | }; 32 | 33 | const handleSelectInputSubmit = () => { 34 | handleOnSubmit(value, path); 35 | if (editingMode === INLINE_EDITING_MODE) { 36 | setSelectedFieldsForEditing((prev) => { 37 | return { 38 | ...prev, 39 | [path]: false, 40 | }; 41 | }); 42 | } 43 | }; 44 | 45 | let disabled = readModeValue === value; 46 | 47 | return ( 48 | <> 49 | 63 | {editingMode !== GLOBAL_EDITING_MODE && ( 64 | 74 | )} 75 | {editingMode === INLINE_EDITING_MODE && } 76 | {(editingMode !== INLINE_EDITING_MODE && !disabled) && } 77 | 78 | ); 79 | } 80 | 81 | export default DefaultSelectInput; 82 | -------------------------------------------------------------------------------- /src/components/JsonEditor/defaultElements/defaultTextAreaInput.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from "react"; 2 | import { DefaultTextAreaElementProps } from "../../../types/JsonEditor.types"; 3 | import { Textarea } from "../../ui/textarea"; 4 | import { debounce, validateValue } from "../../../functions/functions"; 5 | import { DEBOUNCE_DELAY, GLOBAL_EDITING_MODE, INLINE_EDITING_MODE } from "../../../constants/constants"; 6 | import { Button } from "../../ui/button"; 7 | import { Check } from "lucide-react"; 8 | import { useJsonEditorContext } from "../jsonEditor"; 9 | import InlineCancelButton from "../inlineElements/inlineCancelButton"; 10 | import ResetButton from "../inlineElements/resetButton"; 11 | 12 | function DefaultTextAreaElement({ 13 | value, 14 | readModeValue, 15 | path, 16 | fieldValidations 17 | }: DefaultTextAreaElementProps) { 18 | const [textAreaInputValue, setTextAreaInputValue] = useState(value); 19 | const [localValidationError, setLocalValidationError] = useState('') 20 | const { 21 | handleOnChange, 22 | handleOnSubmit, 23 | editingMode, 24 | setSelectedFieldsForEditing, 25 | validations, 26 | setValidations, 27 | debouncing 28 | } = useJsonEditorContext(); 29 | 30 | const handleTextAreaInputChange = ( 31 | e: React.ChangeEvent 32 | ) => { 33 | const value = e.target.value; 34 | let result = null; 35 | if (fieldValidations){ 36 | result = validateValue(value,fieldValidations) 37 | setLocalValidationError(result || '') 38 | } 39 | setTextAreaInputValue(value); 40 | debouncedOnChange(value,result || ""); 41 | }; 42 | 43 | // Memoize debounced onChange with useCallback, recreating when onChange updates. 44 | // This prevents stale closures and ensures the component uses the latest onChange. 45 | const debouncedOnChange = useCallback( 46 | debounce((value: string,validationMessage? : string) => { 47 | const updatedValidations = { 48 | ...validations, 49 | [path] : validationMessage 50 | } 51 | handleOnChange(value, path,updatedValidations); 52 | if (fieldValidations){ 53 | setValidations(prev => { 54 | return { 55 | ...prev, 56 | [path] : validationMessage 57 | } 58 | }) 59 | } 60 | }, debouncing ? DEBOUNCE_DELAY : 0), 61 | [handleOnChange] 62 | ); 63 | 64 | const handleTextAreaInputSubmit = () => { 65 | handleOnSubmit(textAreaInputValue, path); 66 | if (editingMode === INLINE_EDITING_MODE) { 67 | setSelectedFieldsForEditing((prev) => { 68 | return { 69 | ...prev, 70 | [path]: false, 71 | }; 72 | }); 73 | } 74 | }; 75 | 76 | const disabled = readModeValue === textAreaInputValue; 77 | const validationMessage = localValidationError || validations[path] 78 | 79 | return ( 80 | <> 81 |