Skip to content

softwarity/geojson-editor

Repository files navigation

Softwarity

@softwarity/geojson-editor

npm version bundle size license codecov

A feature-rich, framework-agnostic Web Component for editing GeoJSON features with syntax highlighting, collapsible nodes, and integrated color picker.

🚀 Try the Live Demo | 📋 Release Notes

GeoJSON Editor Preview

Why not Monaco, CodeMirror, or Prism?

@softwarity/geojson-editor Monaco Editor CodeMirror 6 Prism.js
Size (gzip) ~2.5 MB* ~150 KB* ~20 KB*
Editable ✅ Full editor ✅ Full editor ✅ Full editor ❌ Read-only
GeoJSON validation ✅ Built-in ❌ Manual ❌ Manual ❌ None
Type highlighting ✅ Contextual ⚠️ Generic JSON ⚠️ Generic JSON ⚠️ Generic JSON
Invalid type detection ✅ Visual feedback
Collapsible nodes ✅ Native ✅ Plugin
Undo/Redo ✅ With grouping
Color picker ✅ Integrated
Boolean checkbox ✅ Integrated
Feature visibility toggle
Auto-collapse coordinates
FeatureCollection output ✅ Always
Clear button
Save to file (Ctrl+S)
Open from file (Ctrl+O)
Error navigation
Dark mode detection ✅ Auto ⚠️ Manual ⚠️ Manual ⚠️ Manual
Dependencies 0 Many Few 0
Setup complexity 1 line Complex Moderate Simple

* Estimated total size: Monaco includes web workers loaded dynamically; CodeMirror/Prism require plugins for equivalent functionality (line numbers, folding, language support).

TL;DR: If you're building a GeoJSON-focused application and need a lightweight, specialized editor with built-in validation and GeoJSON-aware features, this component does exactly that — without the overhead of a general-purpose code editor.

Features

  • Full Editor - Complete editing capabilities with cursor navigation, selection, copy/paste, and keyboard shortcuts — unlike read-only syntax highlighters
  • GeoJSON-Aware Highlighting - Distinct colors for GeoJSON keywords (type, coordinates, geometry, etc.)
  • GeoJSON Type Validation - Valid types (Point, LineString, Polygon, etc.) highlighted distinctly; invalid types (LinearRing, unknown types) shown with error styling (colors configurable via theme)
  • Syntax Highlighting - JSON syntax highlighting with customizable color schemes
  • Collapsible Nodes - Collapse/expand JSON objects and arrays with visual indicators ({...} / [...]); use Enter to expand and Shift+Enter to collapse; coordinates auto-collapsed on load
  • Attribute Navigation - Tab/Shift+Tab to navigate between JSON attributes (keys and values) for quick editing
  • Virtualized Rendering - Monaco-like architecture: only visible lines are rendered to DOM for optimal performance with large GeoJSON files
  • Feature Visibility Toggle - Hide/show individual Features via eye icon in gutter; hidden features are grayed out and excluded from change events (useful for temporary filtering without deleting data)
  • Color Picker - Built-in color swatch for hex color properties (#rrggbb) displayed inline next to the value; click to open native color picker
  • Boolean Checkbox - Inline checkbox for boolean properties displayed next to the value; toggle to switch between true/false and emit changes (e.g., marker: true to show vertices)
  • Dark/Light Color Schemes - Automatic color scheme based on system preference via CSS light-dark() function
  • Auto-format - Automatic JSON formatting in real-time (always enabled)
  • Readonly Mode - Visual indicator with diagonal stripes when editing is disabled
  • Block Editing in Collapsed Areas - Prevents accidental edits in collapsed sections
  • Smart Copy/Paste - Copy includes expanded content even from collapsed nodes
  • FeatureCollection Output - Emits valid FeatureCollection with all edited features
  • Clear Button - Discreet ✕ button in suffix area to clear all editor content (hidden in readonly mode)
  • Undo/Redo - Full undo/redo support with Ctrl+Z / Ctrl+Y / Ctrl+Shift+Z; rapid keystrokes grouped as single undo step
  • Save to File - Ctrl+S to download GeoJSON as .geojson file; programmatic save(filename) method available
  • Open from File - Ctrl+O to open a .geojson or .json file from the client filesystem; programmatic open() method available
  • Error Navigation - Visual error indicators in gutter with navigation buttons (◀ ▶) to jump between errors; error count displayed in suffix area
  • Current Features Event - current-features event emitted as a FeatureCollection when cursor/selection changes; includes all features overlapping with selection; useful for synchronizing with maps

Installation

Option 1: CDN (No build step required)

Simply add a script tag to your HTML file:

<!-- Using unpkg -->
<script type="module" src="https://unpkg.com/@softwarity/geojson-editor"></script>

<!-- Or using jsDelivr -->
<script type="module" src="https://cdn.jsdelivr.net/npm/@softwarity/geojson-editor"></script>

You can also specify a version:

<!-- Specific version -->
<script type="module" src="https://unpkg.com/@softwarity/[email protected]"></script>

<!-- Latest minor/patch of v1 -->
<script type="module" src="https://unpkg.com/@softwarity/geojson-editor@1"></script>

Option 2: NPM (With bundler)

If you're using a bundler (Vite, Webpack, Rollup, etc.):

npm install @softwarity/geojson-editor

Then import in your JavaScript/TypeScript:

import '@softwarity/geojson-editor';

TypeScript Support

This package includes TypeScript type definitions out of the box. No additional @types/* package required.

import '@softwarity/geojson-editor';
import type { SetOptions, CursorPosition, GeometryType } from '@softwarity/geojson-editor';

// Type-safe editor access
const editor = document.querySelector('geojson-editor') as GeoJsonEditor;

// Full autocompletion and type checking
editor.set(features, { collapsed: ['coordinates'] });

Exported types:

  • GeoJsonEditor - The Web Component class
  • SetOptions - Options for set(), add(), insertAt(), open()
  • CursorPosition - Cursor position { line, column }
  • GeometryType - GeoJSON geometry types union

Usage

Basic Usage

<!DOCTYPE html>
<html lang="en">
<head>
  <script type="module" src="https://unpkg.com/@softwarity/geojson-editor"></script>
</head>
<body>
  <!-- User edits features, component wraps in FeatureCollection -->
  <geojson-editor placeholder="Enter GeoJSON features here..."></geojson-editor>
</body>
</html>

Users edit features directly (comma-separated), and the component automatically wraps them in a {"type": "FeatureCollection", "features": [...]} structure for validation and events.

With Theme Control

The component automatically adapts to system color scheme. Override with CSS:

<!-- Force dark mode -->
<geojson-editor style="color-scheme: dark;"></geojson-editor>

<!-- Force light mode -->
<geojson-editor style="color-scheme: light;"></geojson-editor>

Listen to Changes

const editor = document.querySelector('geojson-editor');

// Valid GeoJSON emits change event with parsed object directly
editor.addEventListener('change', (e) => {
  console.log('GeoJSON:', e.detail); // Parsed GeoJSON object
});

// Invalid JSON or GeoJSON validation error emits error event
editor.addEventListener('error', (e) => {
  console.error('Error:', e.detail.error);
  console.log('Errors:', e.detail.errors); // Array of validation errors (if GeoJSON validation)
});

Attributes

Attribute Type Default Description
value string "" Initial editor content (features array content)
placeholder string "" Placeholder text
readonly boolean false Make editor read-only

Note: coordinates nodes are automatically collapsed when content is loaded to improve readability. Use Enter to expand and Shift+Enter to collapse nodes, or click the gutter toggle. Use Tab/Shift+Tab to navigate between attributes.

Themes

The component uses CSS light-dark() function for automatic dark/light mode switching based on the system's color-scheme preference.

Pre-built Themes

Ready-to-use theme CSS files are available:

Theme Description CDN Link
Default (IntelliJ) Built-in, no extra CSS needed -
VS Code Visual Studio Code inspired colors vscode.css
GitHub GitHub's code styling github.css
Monokai Classic dark Monokai palette monokai.css
Solarized Ethan Schoonover's precision colors solarized.css

Using via CDN:

<!-- Include theme CSS before the component -->
<link rel="stylesheet" href="https://unpkg.com/@softwarity/geojson-editor/themes/monokai.css">

<!-- Then use the component -->
<script type="module" src="https://unpkg.com/@softwarity/geojson-editor"></script>

Using via npm:

// Import theme CSS in your build
import '@softwarity/geojson-editor/themes/github.css';
import '@softwarity/geojson-editor';

Custom Themes

Create your own theme by overriding CSS custom properties on :root with the light-dark() function:

:root {
  /* Editor background and text */
  --geojson-editor-bg-color: light-dark(#ffffff, #1e1e1e);
  --geojson-editor-text-color: light-dark(#000000, #d4d4d4);
  --geojson-editor-caret-color: light-dark(#000000, #aeafad);

  /* Gutter (line numbers area) */
  --geojson-editor-gutter-bg: light-dark(#f3f3f3, #1e1e1e);
  --geojson-editor-gutter-border: light-dark(#e0e0e0, #333333);
  --geojson-editor-gutter-text: light-dark(#237893, #858585);

  /* JSON syntax highlighting */
  --geojson-editor-json-key: light-dark(#0451a5, #9cdcfe);
  --geojson-editor-json-string: light-dark(#a31515, #ce9178);
  --geojson-editor-json-number: light-dark(#098658, #b5cea8);
  --geojson-editor-json-boolean: light-dark(#0000ff, #569cd6);
  --geojson-editor-json-punct: light-dark(#000000, #d4d4d4);
  --geojson-editor-json-error: light-dark(#cd3131, #f44747);

  /* GeoJSON-specific */
  --geojson-editor-geojson-key: light-dark(#800000, #9cdcfe);
  --geojson-editor-geojson-type: light-dark(#008000, #4ec9b0);
  --geojson-editor-geojson-type-invalid: light-dark(#cd3131, #f44747);

  /* Controls (checkboxes, color swatches) */
  --geojson-editor-control-color: light-dark(#0000ff, #569cd6);
  --geojson-editor-control-bg: light-dark(#f3f3f3, #3c3c3c);
  --geojson-editor-control-border: light-dark(#c0c0c0, #6b6b6b);

  /* Selection and errors */
  --geojson-editor-selection-color: light-dark(rgba(173, 214, 255, 0.5), rgba(38, 79, 120, 0.5));
  --geojson-editor-error-color: light-dark(#cd3131, #f44747);
}

Forcing Light or Dark Mode

/* Force dark mode on specific editor */
geojson-editor.dark-editor {
  color-scheme: dark;
}

/* Force light mode */
geojson-editor.light-editor {
  color-scheme: light;
}

Integration with CSS Frameworks

The editor inherits color-scheme from parent elements. To integrate with frameworks that use class-based dark mode:

Tailwind CSS (uses html.dark):

html.dark { color-scheme: dark; }
html:not(.dark) { color-scheme: light; }

Bootstrap 5 (uses [data-bs-theme]):

[data-bs-theme="dark"] { color-scheme: dark; }
[data-bs-theme="light"] { color-scheme: light; }

DaisyUI / Other (any class-based theme):

.dark-theme { color-scheme: dark; }
.light-theme { color-scheme: light; }

CSS Custom Properties Reference

Variable Description
--geojson-editor-bg-color Editor background
--geojson-editor-text-color Default text color
--geojson-editor-caret-color Cursor color
--geojson-editor-gutter-bg Line numbers background
--geojson-editor-gutter-border Gutter border color
--geojson-editor-gutter-text Line numbers color
--geojson-editor-json-key JSON property keys
--geojson-editor-json-string String values
--geojson-editor-json-number Number values
--geojson-editor-json-boolean Boolean/null values
--geojson-editor-json-punct Punctuation (brackets, colons)
--geojson-editor-json-error Invalid JSON highlighting
--geojson-editor-geojson-key GeoJSON keywords (type, geometry, etc.)
--geojson-editor-geojson-type Valid geometry types (Point, Polygon, etc.)
--geojson-editor-geojson-type-invalid Invalid geometry types
--geojson-editor-control-color Inline controls accent color
--geojson-editor-control-bg Inline controls background
--geojson-editor-control-border Inline controls border
--geojson-editor-selection-color Text selection background
--geojson-editor-error-color Error indicators

API Methods

const editor = document.querySelector('geojson-editor');

Features API

Programmatic manipulation of features:

Method Description
set(input, options?) Replace all features (throws if invalid)
add(input, options?) Add features at the end (throws if invalid)
insertAt(input, index, options?) Insert at index (negative = from end: -1 = before last) (throws if invalid)
removeAt(index) Remove feature at index (negative = from end), returns removed feature
removeAll() Remove all features, returns array of removed features
get(index) Get feature at index (negative = from end)
getAll() Get all features as an array
emit() Emit the current document on the change event

Flexible Input: set(), add(), and insertAt() accept multiple input formats:

  • FeatureCollection → extracts the features array
  • Feature[] → uses the array directly
  • Feature → wraps in an array

Options Parameter: set(), add(), insertAt(), and open() accept an optional options object:

Option Type Default Description
collapsed string[] | function ['coordinates'] Attributes to collapse after loading

The collapsed option controls which JSON nodes are automatically collapsed:

  • string[]: Static list of attribute names to collapse (e.g., ['coordinates', 'properties'])
  • function(feature, index) => string[]: Dynamic function returning attributes per feature
  • '$root': Special keyword to collapse entire features
  • Empty array []: Disables auto-collapse (nothing collapsed)
// Default: only coordinates collapsed
editor.set(features);

// Collapse coordinates and properties
editor.set(features, { collapsed: ['coordinates', 'properties'] });

// Collapse entire features
editor.set(features, { collapsed: ['$root'] });

// No auto-collapse
editor.set(features, { collapsed: [] });

// Dynamic: collapse geometry only for Points
editor.set(features, {
  collapsed: (feature, index) => {
    if (feature.geometry?.type === 'Point') {
      return ['geometry'];
    }
    return ['coordinates'];
  }
});

Validation: All input features are validated before adding. Invalid features throw an Error with a descriptive message. A valid Feature must have:

  • type: "Feature"
  • geometry: object with valid type (Point, LineString, Polygon, etc.) and coordinates, or null
  • properties: object or null

Smart Paste: When pasting GeoJSON content (Ctrl+V), the editor automatically detects and normalizes the format (FeatureCollection, Feature[], or single Feature). Invalid GeoJSON falls back to raw text insertion.

// Set features from array
editor.set([
  { type: 'Feature', geometry: { type: 'Point', coordinates: [0, 0] }, properties: {} },
  { type: 'Feature', geometry: { type: 'Point', coordinates: [1, 1] }, properties: {} }
]);

// Set from FeatureCollection
editor.set({
  type: 'FeatureCollection',
  features: [{ type: 'Feature', geometry: { type: 'Point', coordinates: [0, 0] }, properties: {} }]
});

// Set a single feature
editor.set({ type: 'Feature', geometry: { type: 'Point', coordinates: [2, 2] }, properties: {} });

// Add multiple features at once
editor.add([feature1, feature2]);

// Insert FeatureCollection at position 1
editor.insertAt(featureCollection, 1);

// Remove last feature
const removed = editor.removeAt(-1);

// Get all features
const features = editor.getAll();

// Manually emit change event
editor.emit();

Undo/Redo API

Full undo/redo support with action grouping:

Method Description
undo() Undo last action, returns true if successful
redo() Redo previously undone action, returns true if successful
canUndo() Check if undo is available
canRedo() Check if redo is available
clearHistory() Clear undo/redo history

Keyboard shortcuts:

  • Ctrl+Z / Cmd+Z - Undo
  • Ctrl+Y / Cmd+Y - Redo
  • Ctrl+Shift+Z / Cmd+Shift+Z - Redo (alternative)

Action grouping: Rapid keystrokes (< 500ms apart, same action type) are automatically grouped as a single undo step. This means typing "hello" quickly creates one undo entry, not five.

// Programmatic undo/redo
if (editor.canUndo()) {
  editor.undo();
}

// Clear history after saving
editor.clearHistory();

Save API

Save the current GeoJSON content to a file:

Method Description
save(filename?) Download GeoJSON as file, returns true if successful

Keyboard shortcut:

  • Ctrl+S / Cmd+S - Save with default filename (features.geojson)
// Save with default filename
editor.save();  // Downloads "features.geojson"

// Save with custom filename
editor.save('my-map-data.geojson');

Open API

Open a GeoJSON file from the client filesystem:

Method Description
open(options?) Open file dialog, returns Promise<boolean> (true if loaded successfully)

Keyboard shortcut:

  • Ctrl+O / Cmd+O - Open file dialog

Supported formats:

  • FeatureCollection (extracts features array)
  • Single Feature (wraps in array)
  • Array of Features

Note: The Ctrl+O shortcut is disabled in readonly mode, but open() remains available via API for programmatic loading.

// Open file dialog
const success = await editor.open();
if (success) {
  console.log('File loaded:', editor.getAll());
}

// Open with custom collapse options
const success = await editor.open({ collapsed: ['$root'] });

Error Navigation API

Navigate between syntax and structural errors:

Method Description
goToNextError() Navigate to next error, returns true if found
goToPrevError() Navigate to previous error, returns true if found

Error lines are indicated with a red bar in the gutter. The error count is displayed in the suffix area between navigation buttons (◀ ▶).

// Navigate to errors programmatically
editor.goToNextError();
editor.goToPrevError();

Keyboard Shortcuts

Shortcut Action
Ctrl+S / Cmd+S Save to file
Ctrl+O / Cmd+O Open file (disabled in readonly)
Ctrl+Z / Cmd+Z Undo
Ctrl+Y / Cmd+Y Redo
Ctrl+Shift+Z / Cmd+Shift+Z Redo (alternative)
Ctrl+A / Cmd+A Select all
Ctrl+C / Cmd+C Copy
Ctrl+X / Cmd+X Cut
Ctrl+V / Cmd+V Paste
Ctrl+← / Cmd+← Move cursor to previous word
Ctrl+→ / Cmd+→ Move cursor to next word
Ctrl+Shift+← / Cmd+Shift+← Select to previous word
Ctrl+Shift+→ / Cmd+Shift+→ Select to next word
Enter Expand collapsed node at cursor
Shift+Enter Collapse innermost node containing cursor
Tab Navigate to next attribute (key or value)
Shift+Tab Navigate to previous attribute
Home Go to start of line
End Go to end of line
Ctrl+Home / Cmd+Home Go to start of document
Ctrl+End / Cmd+End Go to end of document
PageUp Scroll up one page
PageDown Scroll down one page
Shift+Home/End/PageUp/PageDown Extend selection while navigating
Ctrl+I / Cmd+I Add feature via prompt (requires internal-add-shortcut attribute)

Overriding Shortcuts

All keyboard shortcuts handled by the editor call stopPropagation() to prevent them from bubbling up. To override a default shortcut behavior, use a capture-phase event listener:

editor.addEventListener('keydown', async (e) => {
  // Override Ctrl+S to save to a remote server instead of downloading
  if (e.ctrlKey && e.key === 's') {
    e.preventDefault();
    e.stopPropagation();
    await saveToRemoteServer(editor.getAll());
  }
  // Override Ctrl+O to load from a remote API instead of local filesystem
  if (e.ctrlKey && e.key === 'o') {
    e.preventDefault();
    e.stopPropagation();
    const features = await openRemoteFeatureSelector();
    if (features) editor.set(features);
  }
}, { capture: true });  // capture: true is required to intercept before the editor

Events

change

Fired when content changes and GeoJSON is valid (debounced 150ms).

editor.addEventListener('change', (e) => {
  console.log(e.detail);  // Parsed GeoJSON object directly
});

Event detail: The parsed GeoJSON object (always a FeatureCollection).

Note: Hidden features (toggled via the eye icon) are automatically excluded from the emitted GeoJSON. This allows temporary filtering without modifying the actual JSON content.

Example:

// User edits features only, but change event includes the FeatureCollection wrapper
editor.addEventListener('change', (e) => {
  console.log(e.detail);
  // → { type: "FeatureCollection", features: [{ type: "Feature", ... }] }
});

error

Fired when content changes but JSON is invalid or GeoJSON validation fails (debounced 150ms).

editor.addEventListener('error', (e) => {
  console.error(e.detail.error);   // Error message
  console.log(e.detail.errors);    // Array of validation errors (GeoJSON validation only)
  console.log(e.detail.content);   // Raw content for debugging
});

Event detail properties:

Property Type Description
error string Error message (JSON parse error or GeoJSON validation summary)
errors string[] Array of validation errors with paths (GeoJSON validation only)
content string Raw editor content (for debugging)

GeoJSON validation errors include:

  • Invalid types (e.g., "LinearRing")
  • Unknown types (any type value not in the GeoJSON specification)

current-features

Fired when the current feature(s) change. Useful for synchronizing the editor with a map display. Emits a FeatureCollection containing all features that overlap with the cursor position or selection.

Triggers:

  • Editor gains focus → emits FeatureCollection with feature at cursor
  • Cursor moves to a different feature → emits FeatureCollection with new feature(s)
  • Selection spans multiple features → emits FeatureCollection with all overlapping features
  • Cursor moves outside any feature → emits empty FeatureCollection
  • Editor loses focus → emits empty FeatureCollection
editor.addEventListener('current-features', (e) => {
  const featureCollection = e.detail;  // Always a FeatureCollection
  if (featureCollection.features.length > 0) {
    // Highlight these features on your map
    highlightLayer.setData(featureCollection);
  } else {
    // No current features
    highlightLayer.setData({ type: 'FeatureCollection', features: [] });
  }
});

Event detail: A FeatureCollection object containing:

  • All features overlapping with the current selection (if selection exists)
  • The single feature at cursor position (if no selection)
  • Empty features array if cursor is outside any feature or editor loses focus

Note: The event is only fired when the set of features changes, not on every cursor movement within the same feature. This prevents excessive event firing during normal editing.

Styling

The component uses Shadow DOM with CSS variables for theming. Customize colors via CSS custom properties (see Theme Control).

Browser Support

Works in all modern browsers supporting:

  • Web Components
  • Shadow DOM
  • ES6 Modules

Development

See DEVELOPMENT.md for detailed development guide.

# Install dependencies
npm install

# Start dev server with live demo
npm run dev

# Build for production
npm run build

License

MIT