Current section
17 Versions
Jump to
Current section
17 Versions
Compare versions
8
files changed
+329
additions
-13
deletions
| @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file. | |
| 5 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), |
| 6 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). |
| 7 7 | |
| 8 | + ## [0.9.8] - 2026-02-01 |
| 9 | + |
| 10 | + ### Added |
| 11 | + - Support for custom schema extensions (`x-` properties) at any node level |
| 12 | + - Automatic type casting for extension values (number, boolean, null) |
| 13 | + - Dedicated UI section for managing custom extensions with a new 'tag' icon |
| 14 | + |
| 8 15 | ## [0.9.7] - 2026-01-27 |
| 9 16 | |
| 10 17 | ### Added |
| @@ -15,6 +15,7 @@ A Phoenix LiveComponent for visually building, editing, and validating JSON Sche | |
| 15 15 | - **Undo/Redo**: Full history support for all schema modifications. |
| 16 16 | - **Real-time Validation**: In-editor logic checking (e.g., `min <= max`) with immediate visual feedback. |
| 17 17 | - **Draft 07 Support**: Includes constraints, enums, constants, `null` type, and $schema management. |
| 18 | + - **Custom Extensions**: Support for adding and editing custom `x-` properties at any node level. |
| 18 19 | - **Copy to Clipboard**: One-click export of the generated schema. |
| 19 20 | - **Lightweight**: Zero external JS dependencies (uses native Phoenix hooks), only requires `phoenix_live_view`. |
| 20 21 | - **JSON Viewer**: Dedicated component for displaying JSON with syntax highlighting and indentation guides. |
| @@ -29,7 +30,7 @@ This library uses a small CSS file for styling and a JavaScript hook for clipboa | |
| 29 30 | ```elixir |
| 30 31 | def deps do |
| 31 32 | [ |
| 32 | - {:json_schema_editor, "~> 0.9.6"} |
| 33 | + {:json_schema_editor, "~> 0.9.7"} |
| 33 34 | ] |
| 34 35 | end |
| 35 36 | ``` |
| @@ -85,9 +86,14 @@ end | |
| 85 86 | module={JSONSchemaEditor} |
| 86 87 | id="json-editor" |
| 87 88 | schema={@my_schema} |
| 89 | + on_change={fn updated_schema -> send(self(), {:schema_changed, updated_schema}) end} |
| 88 90 | on_save={fn updated_schema -> send(self(), {:schema_saved, updated_schema}) end} |
| 91 | + class="my-custom-theme" |
| 92 | + header_class="bg-gray-100 p-4" |
| 93 | + toolbar_class="flex gap-2" |
| 89 94 | /> |
| 90 95 | ``` |
| 96 | + *Note: Both `on_save` and `on_change` are optional. If `on_save` is omitted, the "Save" button will not be rendered. `on_change` triggers on every valid update.* |
| 91 97 | |
| 92 98 | ### 3. Handle Updates |
| 93 99 | |
| @@ -96,6 +102,11 @@ def handle_info({:schema_saved, updated_schema}, socket) do | |
| 96 102 | # The schema is guaranteed to be logically consistent here |
| 97 103 | {:noreply, assign(socket, my_schema: updated_schema)} |
| 98 104 | end |
| 105 | + |
| 106 | + def handle_info({:schema_changed, updated_schema}, socket) do |
| 107 | + # Triggered whenever the schema is modified and valid |
| 108 | + {:noreply, assign(socket, my_schema: updated_schema)} |
| 109 | + end |
| 99 110 | ``` |
| 100 111 | |
| 101 112 | ### JSON Viewer |
| @@ -118,12 +129,30 @@ The library also includes a dedicated JSON Editor component for editing JSON dat | |
| 118 129 | id="json-data-editor" |
| 119 130 | schema={@my_schema} |
| 120 131 | json={@my_data} |
| 132 | + on_change={fn updated_json -> send(self(), {:json_changed, updated_json}) end} |
| 121 133 | on_save={fn updated_json -> send(self(), {:json_updated, updated_json}) end} |
| 122 134 | /> |
| 123 135 | ``` |
| 124 136 | |
| 125 137 | It features real-time validation, a visual tree editor, and a live preview of the edited data. |
| 126 138 | |
| 139 | + ## Theming |
| 140 | + |
| 141 | + The editor uses CSS variables for styling, which can be easily overridden in your project's CSS or via the `class` attribute. |
| 142 | + |
| 143 | + ```css |
| 144 | + .my-custom-theme { |
| 145 | + /* Override main colors */ |
| 146 | + --jse-primary: #ec4899; |
| 147 | + --jse-primary-hover: #db2777; |
| 148 | + |
| 149 | + /* Override layout dimensions */ |
| 150 | + --jse-tree-row-height: 2rem; |
| 151 | + } |
| 152 | + ``` |
| 153 | + |
| 154 | + See `assets/css/json_schema_editor.css` for the full list of available variables. |
| 155 | + |
| 127 156 | ## Development |
| 128 157 | |
| 129 158 | ### Running Tests |
| @@ -432,7 +432,7 @@ | |
| 432 432 | border-color: var(--_jse-border-focus); |
| 433 433 | } |
| 434 434 | |
| 435 | - .jse-constraints-container { |
| 435 | + .jse-constraints-container, .jse-extensions-container { |
| 436 436 | background-color: var(--_jse-bg-secondary); |
| 437 437 | border: 1px solid var(--_jse-border); |
| 438 438 | border-radius: var(--_jse-radius); |
| @@ -440,6 +440,81 @@ | |
| 440 440 | margin-bottom: 1rem; |
| 441 441 | } |
| 442 442 | |
| 443 | + .jse-extensions-header { |
| 444 | + display: flex; |
| 445 | + align-items: center; |
| 446 | + justify-content: space-between; |
| 447 | + margin-bottom: 0.75rem; |
| 448 | + padding-bottom: 0.25rem; |
| 449 | + border-bottom: 1px dashed var(--_jse-border); |
| 450 | + } |
| 451 | + |
| 452 | + .jse-extensions-list { |
| 453 | + display: flex; |
| 454 | + flex-direction: column; |
| 455 | + gap: 0.5rem; |
| 456 | + } |
| 457 | + |
| 458 | + .jse-extension-item { |
| 459 | + background-color: var(--_jse-bg); |
| 460 | + border: 1px solid var(--_jse-border); |
| 461 | + border-radius: var(--_jse-radius); |
| 462 | + padding: 0.25rem 0.5rem; |
| 463 | + } |
| 464 | + |
| 465 | + .jse-extension-row { |
| 466 | + display: flex; |
| 467 | + align-items: center; |
| 468 | + gap: 0.5rem; |
| 469 | + } |
| 470 | + |
| 471 | + .jse-extension-key-input { |
| 472 | + background: transparent; |
| 473 | + border: none; |
| 474 | + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; |
| 475 | + font-size: 0.875rem; |
| 476 | + font-weight: 600; |
| 477 | + color: var(--_jse-syntax-key); |
| 478 | + width: 150px; |
| 479 | + border-radius: 2px; |
| 480 | + } |
| 481 | + |
| 482 | + .jse-extension-value-input { |
| 483 | + flex: 1; |
| 484 | + background: transparent; |
| 485 | + border: none; |
| 486 | + font-size: 0.875rem; |
| 487 | + color: var(--_jse-text-primary); |
| 488 | + border-radius: 2px; |
| 489 | + } |
| 490 | + |
| 491 | + .jse-extension-key-input:hover, .jse-extension-value-input:hover { |
| 492 | + background-color: var(--_jse-bg-secondary); |
| 493 | + } |
| 494 | + |
| 495 | + .jse-extension-key-input:focus, .jse-extension-value-input:focus { |
| 496 | + outline: none; |
| 497 | + background-color: var(--_jse-bg-secondary); |
| 498 | + box-shadow: 0 0 0 1px var(--_jse-primary); |
| 499 | + } |
| 500 | + |
| 501 | + .jse-no-data { |
| 502 | + font-size: 0.875rem; |
| 503 | + color: var(--_jse-text-tertiary); |
| 504 | + font-style: italic; |
| 505 | + text-align: center; |
| 506 | + padding: 0.5rem; |
| 507 | + } |
| 508 | + |
| 509 | + .jse-btn-toggle-extensions.jse-active { |
| 510 | + color: var(--_jse-primary); |
| 511 | + background-color: var(--_jse-bg-secondary); |
| 512 | + } |
| 513 | + |
| 514 | + .jse-btn-toggle-extensions.jse-has-data { |
| 515 | + color: var(--_jse-primary); |
| 516 | + } |
| 517 | + |
| 443 518 | .jse-constraints-grid { |
| 444 519 | display: grid; |
| 445 520 | grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); |
| @@ -1,26 +1,27 @@ | |
| 1 1 | {<<"links">>, |
| 2 2 | [{<<"GitHub">>,<<"https://github.com/preciz/json_schema_editor">>}]}. |
| 3 3 | {<<"name">>,<<"json_schema_editor">>}. |
| 4 | - {<<"version">>,<<"0.9.7">>}. |
| 4 | + {<<"version">>,<<"0.9.8">>}. |
| 5 5 | {<<"description">>, |
| 6 6 | <<"A Phoenix LiveComponent for visually building, editing, and validating JSON Schemas.">>}. |
| 7 7 | {<<"elixir">>,<<"~> 1.18">>}. |
| 8 8 | {<<"app">>,<<"json_schema_editor">>}. |
| 9 9 | {<<"files">>, |
| 10 10 | [<<"lib">>,<<"lib/json_schema_editor">>, |
| 11 | - <<"lib/json_schema_editor/components.ex">>, |
| 12 11 | <<"lib/json_schema_editor/validator.ex">>, |
| 13 12 | <<"lib/json_schema_editor/viewer.ex">>, |
| 14 13 | <<"lib/json_schema_editor/ui_state.ex">>, |
| 15 | - <<"lib/json_schema_editor/schema_mutator.ex">>, |
| 16 14 | <<"lib/json_schema_editor/simple_validator.ex">>, |
| 17 15 | <<"lib/json_schema_editor/schema_generator.ex">>, |
| 18 16 | <<"lib/json_schema_editor/pretty_printer.ex">>, |
| 19 17 | <<"lib/json_schema_editor/schema_utils.ex">>, |
| 20 | - <<"lib/json_schema_editor/json_editor.ex">>,<<"lib/json_schema_editor.ex">>, |
| 21 | - <<"assets">>,<<"assets/css">>,<<"assets/css/json_schema_editor.css">>, |
| 22 | - <<"assets/js">>,<<"assets/js/json_schema_editor.js">>,<<".formatter.exs">>, |
| 23 | - <<"mix.exs">>,<<"README.md">>,<<"LICENSE">>,<<"CHANGELOG.md">>]}. |
| 18 | + <<"lib/json_schema_editor/json_editor.ex">>, |
| 19 | + <<"lib/json_schema_editor/components.ex">>, |
| 20 | + <<"lib/json_schema_editor/schema_mutator.ex">>, |
| 21 | + <<"lib/json_schema_editor.ex">>,<<"assets">>,<<"assets/css">>, |
| 22 | + <<"assets/css/json_schema_editor.css">>,<<"assets/js">>, |
| 23 | + <<"assets/js/json_schema_editor.js">>,<<".formatter.exs">>,<<"mix.exs">>, |
| 24 | + <<"README.md">>,<<"LICENSE">>,<<"CHANGELOG.md">>]}. |
| 24 25 | {<<"licenses">>,[<<"MIT">>]}. |
| 25 26 | {<<"requirements">>, |
| 26 27 | [[{<<"name">>,<<"phoenix_live_view">>}, |
| @@ -4,7 +4,7 @@ defmodule JSONSchemaEditor do | |
| 4 4 | |
| 5 5 | It provides a rich interface for creating and modifying JSON Schemas (Draft 07), supporting |
| 6 6 | nested structures, arrays, validation constraints, logical composition (oneOf, anyOf, allOf), |
| 7 | - and conditional logic (if, then, else, not). |
| 7 | + conditional logic (if, then, else, not), and custom extensions (x- properties). |
| 8 8 | |
| 9 9 | The library also includes a dedicated `JSONSchemaEditor.JSONEditor` component for editing |
| 10 10 | JSON data according to a provided schema. |
| @@ -282,9 +282,14 @@ defmodule JSONSchemaEditor do | |
| 282 282 | new_ui_state = |
| 283 283 | Map.update(socket.assigns.ui_state, "#{t}:#{p}", true, fn v -> !v end) |
| 284 284 | |
| 285 | - # If we are expanding constraints, logic or description, ensure the node itself is not collapsed |
| 285 | + # If we are expanding constraints, logic, extensions or description, ensure the node itself is not collapsed |
| 286 286 | final_ui_state = |
| 287 | - if t in ["expanded_constraints", "expanded_logic", "expanded_description"] and |
| 287 | + if t in [ |
| 288 | + "expanded_constraints", |
| 289 | + "expanded_logic", |
| 290 | + "expanded_description", |
| 291 | + "expanded_extensions" |
| 292 | + ] and |
| 288 293 | Map.get(new_ui_state, "#{t}:#{p}") do |
| 289 294 | Map.put(new_ui_state, "collapsed_node:#{p}", false) |
| 290 295 | else |
| @@ -294,6 +299,42 @@ defmodule JSONSchemaEditor do | |
| 294 299 | {:noreply, assign(socket, :ui_state, final_ui_state)} |
| 295 300 | end |
| 296 301 | |
| 302 | + def handle_event("add_extension", %{"path" => path}, socket) do |
| 303 | + {new_schema, _new_key} = SchemaMutator.add_extension(socket.assigns.schema, path) |
| 304 | + {:noreply, update_schema_and_notify(socket, new_schema)} |
| 305 | + end |
| 306 | + |
| 307 | + def handle_event("delete_extension", %{"path" => path, "key" => key}, socket) do |
| 308 | + {:noreply, |
| 309 | + update_schema_and_notify( |
| 310 | + socket, |
| 311 | + SchemaMutator.delete_extension(socket.assigns.schema, path, key) |
| 312 | + )} |
| 313 | + end |
| 314 | + |
| 315 | + def handle_event( |
| 316 | + "update_extension_key", |
| 317 | + %{"path" => path, "old_key" => old, "value" => new}, |
| 318 | + socket |
| 319 | + ) do |
| 320 | + case SchemaMutator.update_extension_key(socket.assigns.schema, path, old, new) do |
| 321 | + {:ok, new_schema} -> {:noreply, update_schema_and_notify(socket, new_schema)} |
| 322 | + _ -> {:noreply, socket} |
| 323 | + end |
| 324 | + end |
| 325 | + |
| 326 | + def handle_event( |
| 327 | + "update_extension_value", |
| 328 | + %{"path" => path, "key" => key, "value" => value}, |
| 329 | + socket |
| 330 | + ) do |
| 331 | + {:noreply, |
| 332 | + update_schema_and_notify( |
| 333 | + socket, |
| 334 | + SchemaMutator.update_extension_value(socket.assigns.schema, path, key, value) |
| 335 | + )} |
| 336 | + end |
| 337 | + |
| 297 338 | # --- Simple Mutations (Schema update only) --- |
| 298 339 | |
| 299 340 | def handle_event(event, params, socket) do |
Loading more files…