@softwarity/geojson-editor 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +276 -0
- package/dist/geojson-editor.js +10 -0
- package/package.json +44 -0
- package/src/geojson-editor.js +1825 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Softwarity
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://www.softwarity.io/">
|
|
3
|
+
<img src="https://www.softwarity.io/img/softwarity.svg" alt="Softwarity" height="60">
|
|
4
|
+
</a>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
# @softwarity/geojson-editor
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="https://www.npmjs.com/package/@softwarity/geojson-editor">
|
|
11
|
+
<img src="https://img.shields.io/npm/v/@softwarity/geojson-editor?color=blue&label=npm" alt="npm version">
|
|
12
|
+
</a>
|
|
13
|
+
<a href="https://www.npmjs.com/package/@softwarity/geojson-editor">
|
|
14
|
+
<img src="https://img.shields.io/npm/dm/@softwarity/geojson-editor?color=green" alt="npm downloads">
|
|
15
|
+
</a>
|
|
16
|
+
<a href="https://bundlephobia.com/package/@softwarity/geojson-editor">
|
|
17
|
+
<img src="https://img.shields.io/bundlephobia/minzip/@softwarity/geojson-editor?label=size" alt="bundle size">
|
|
18
|
+
</a>
|
|
19
|
+
<a href="https://github.com/softwarity/geojson-editor/blob/main/LICENSE">
|
|
20
|
+
<img src="https://img.shields.io/npm/l/@softwarity/geojson-editor" alt="license">
|
|
21
|
+
</a>
|
|
22
|
+
</p>
|
|
23
|
+
|
|
24
|
+
A feature-rich, framework-agnostic **Web Component** for editing GeoJSON features with syntax highlighting, collapsible nodes, and integrated color picker.
|
|
25
|
+
|
|
26
|
+
**[🚀 Try the Live Demo](https://softwarity.github.io/geojson-editor/)**
|
|
27
|
+
|
|
28
|
+
## Why not Monaco, CodeMirror, or Prism?
|
|
29
|
+
|
|
30
|
+
| | @softwarity/geojson-editor | Monaco Editor | CodeMirror | Prism.js |
|
|
31
|
+
|---|:---:|:---:|:---:|:---:|
|
|
32
|
+
| **Size (gzip)** | ~10 KB | ~2.5 MB | ~150 KB | ~15 KB + plugins |
|
|
33
|
+
| **GeoJSON validation** | ✅ Built-in | ❌ Manual | ❌ Manual | ❌ None |
|
|
34
|
+
| **Type highlighting** | ✅ Contextual | ⚠️ Generic JSON | ⚠️ Generic JSON | ⚠️ Generic JSON |
|
|
35
|
+
| **Invalid type detection** | ✅ Visual feedback | ❌ | ❌ | ❌ |
|
|
36
|
+
| **Collapsible nodes** | ✅ Native | ✅ | ✅ Plugin | ❌ |
|
|
37
|
+
| **Color picker** | ✅ Integrated | ❌ | ❌ | ❌ |
|
|
38
|
+
| **Auto-collapse coordinates** | ✅ | ❌ | ❌ | ❌ |
|
|
39
|
+
| **FeatureCollection mode** | ✅ | ❌ | ❌ | ❌ |
|
|
40
|
+
| **Dark mode detection** | ✅ Auto | ⚠️ Manual | ⚠️ Manual | ⚠️ Manual |
|
|
41
|
+
| **Dependencies** | 0 | Many | Few | 0 |
|
|
42
|
+
| **Setup complexity** | 1 line | Complex | Moderate | Simple |
|
|
43
|
+
|
|
44
|
+
**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.
|
|
45
|
+
|
|
46
|
+
## Features
|
|
47
|
+
|
|
48
|
+
- **GeoJSON-Aware Highlighting** - Distinct colors for GeoJSON keywords (`type`, `coordinates`, `geometry`, etc.)
|
|
49
|
+
- **GeoJSON Type Validation** - Valid types (`Point`, `LineString`, `Polygon`, etc.) highlighted distinctly; invalid types (`LinearRing`, unknown types) shown with error styling (colors configurable via theme)
|
|
50
|
+
- **Syntax Highlighting** - JSON syntax highlighting with customizable color schemes
|
|
51
|
+
- **Collapsible Nodes** - Collapse/expand JSON objects and arrays with visual indicators (`{...}` / `[...]`); `coordinates` auto-collapsed on load
|
|
52
|
+
- **Color Picker** - Built-in color picker for color properties in left gutter
|
|
53
|
+
- **Dark/Light Themes** - Automatic theme detection from parent page (Bootstrap, Tailwind, custom)
|
|
54
|
+
- **Auto-format** - Optional automatic JSON formatting in real-time
|
|
55
|
+
- **Readonly Mode** - Visual indicator with diagonal stripes when editing is disabled
|
|
56
|
+
- **Block Editing in Collapsed Areas** - Prevents accidental edits in collapsed sections
|
|
57
|
+
- **Smart Copy/Paste** - Copy includes expanded content even from collapsed nodes
|
|
58
|
+
- **FeatureCollection Mode** - Optional mode to auto-wrap features in a FeatureCollection structure
|
|
59
|
+
|
|
60
|
+
## Installation
|
|
61
|
+
|
|
62
|
+
### Option 1: CDN (No build step required)
|
|
63
|
+
|
|
64
|
+
Simply add a script tag to your HTML file:
|
|
65
|
+
|
|
66
|
+
```html
|
|
67
|
+
<!-- Using unpkg -->
|
|
68
|
+
<script type="module" src="https://unpkg.com/@softwarity/geojson-editor"></script>
|
|
69
|
+
|
|
70
|
+
<!-- Or using jsDelivr -->
|
|
71
|
+
<script type="module" src="https://cdn.jsdelivr.net/npm/@softwarity/geojson-editor"></script>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
You can also specify a version:
|
|
75
|
+
|
|
76
|
+
```html
|
|
77
|
+
<!-- Specific version -->
|
|
78
|
+
<script type="module" src="https://unpkg.com/@softwarity/geojson-editor@1.0.0"></script>
|
|
79
|
+
|
|
80
|
+
<!-- Latest minor/patch of v1 -->
|
|
81
|
+
<script type="module" src="https://unpkg.com/@softwarity/geojson-editor@1"></script>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Option 2: NPM (With bundler)
|
|
85
|
+
|
|
86
|
+
If you're using a bundler (Vite, Webpack, Rollup, etc.):
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
npm install @softwarity/geojson-editor
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Then import in your JavaScript:
|
|
93
|
+
|
|
94
|
+
```javascript
|
|
95
|
+
import '@softwarity/geojson-editor';
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Usage
|
|
99
|
+
|
|
100
|
+
### Basic Usage (FeatureCollection mode)
|
|
101
|
+
|
|
102
|
+
```html
|
|
103
|
+
<!DOCTYPE html>
|
|
104
|
+
<html lang="en">
|
|
105
|
+
<head>
|
|
106
|
+
<script type="module" src="https://unpkg.com/@softwarity/geojson-editor"></script>
|
|
107
|
+
</head>
|
|
108
|
+
<body>
|
|
109
|
+
<!-- User edits features, component wraps in FeatureCollection -->
|
|
110
|
+
<geojson-editor
|
|
111
|
+
feature-collection
|
|
112
|
+
placeholder="Enter GeoJSON features here..."
|
|
113
|
+
></geojson-editor>
|
|
114
|
+
</body>
|
|
115
|
+
</html>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Standalone Mode (Full GeoJSON)
|
|
119
|
+
|
|
120
|
+
```html
|
|
121
|
+
<!-- User edits a complete GeoJSON object (Feature or FeatureCollection) -->
|
|
122
|
+
<geojson-editor
|
|
123
|
+
placeholder="Enter GeoJSON here..."
|
|
124
|
+
></geojson-editor>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### With Auto-format and Theme Detection
|
|
128
|
+
|
|
129
|
+
```html
|
|
130
|
+
<geojson-editor
|
|
131
|
+
feature-collection
|
|
132
|
+
dark-selector="html.dark"
|
|
133
|
+
auto-format
|
|
134
|
+
></geojson-editor>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Listen to Changes
|
|
138
|
+
|
|
139
|
+
```javascript
|
|
140
|
+
const editor = document.querySelector('geojson-editor');
|
|
141
|
+
|
|
142
|
+
// Valid GeoJSON emits change event with parsed object directly
|
|
143
|
+
editor.addEventListener('change', (e) => {
|
|
144
|
+
console.log('GeoJSON:', e.detail); // Parsed GeoJSON object
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Invalid JSON or GeoJSON validation error emits error event
|
|
148
|
+
editor.addEventListener('error', (e) => {
|
|
149
|
+
console.error('Error:', e.detail.error);
|
|
150
|
+
console.log('Errors:', e.detail.errors); // Array of validation errors (if GeoJSON validation)
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Attributes
|
|
155
|
+
|
|
156
|
+
| Attribute | Type | Default | Description |
|
|
157
|
+
|-----------|------|---------|-------------|
|
|
158
|
+
| `value` | `string` | `""` | Initial editor content |
|
|
159
|
+
| `placeholder` | `string` | `""` | Placeholder text |
|
|
160
|
+
| `readonly` | `boolean` | `false` | Make editor read-only |
|
|
161
|
+
| `auto-format` | `boolean` | `false` | Auto-format JSON on input |
|
|
162
|
+
| `dark-selector` | `string` | `".dark"` | CSS selector for dark theme (if matches → dark, else → light) |
|
|
163
|
+
| `feature-collection` | `boolean` | `false` | When set, wraps editor content in a FeatureCollection for validation/events |
|
|
164
|
+
|
|
165
|
+
**Note:** `coordinates` nodes are automatically collapsed when content is loaded to improve readability. All nodes can be manually expanded/collapsed by clicking the toggle button.
|
|
166
|
+
|
|
167
|
+
### Dark Selector Syntax
|
|
168
|
+
|
|
169
|
+
The `dark-selector` attribute determines when the dark theme is active. If the selector matches, dark theme is applied; otherwise, light theme is used.
|
|
170
|
+
|
|
171
|
+
**Examples:**
|
|
172
|
+
|
|
173
|
+
- `.dark` - Component has `dark` class: `<geojson-editor class="dark">`
|
|
174
|
+
- `html.dark` - HTML element has `dark` class (Tailwind CSS): `<html class="dark">`
|
|
175
|
+
- `html[data-bs-theme=dark]` - HTML has Bootstrap theme attribute: `<html data-bs-theme="dark">`
|
|
176
|
+
- Empty string `""` - Uses component's `data-color-scheme` attribute as fallback
|
|
177
|
+
|
|
178
|
+
## API Methods
|
|
179
|
+
|
|
180
|
+
```javascript
|
|
181
|
+
const editor = document.querySelector('geojson-editor');
|
|
182
|
+
|
|
183
|
+
// Get/set theme
|
|
184
|
+
const themes = editor.getTheme();
|
|
185
|
+
editor.setTheme({
|
|
186
|
+
dark: { background: '#000', textColor: '#fff' },
|
|
187
|
+
light: { background: '#fff', textColor: '#000' }
|
|
188
|
+
});
|
|
189
|
+
editor.resetTheme();
|
|
190
|
+
|
|
191
|
+
// Get/set value
|
|
192
|
+
editor.setAttribute('value', JSON.stringify(data));
|
|
193
|
+
const value = editor.querySelector('#textarea').value; // via Shadow DOM
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Events
|
|
197
|
+
|
|
198
|
+
### `change`
|
|
199
|
+
|
|
200
|
+
Fired when content changes and GeoJSON is valid (debounced 150ms).
|
|
201
|
+
|
|
202
|
+
```javascript
|
|
203
|
+
editor.addEventListener('change', (e) => {
|
|
204
|
+
console.log(e.detail); // Parsed GeoJSON object directly
|
|
205
|
+
});
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**Event detail:** The parsed GeoJSON object directly. In `feature-collection` mode, the wrapper is included.
|
|
209
|
+
|
|
210
|
+
**Example with FeatureCollection mode:**
|
|
211
|
+
|
|
212
|
+
```html
|
|
213
|
+
<geojson-editor feature-collection></geojson-editor>
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
```javascript
|
|
217
|
+
// User edits features only, but change event includes the FeatureCollection wrapper
|
|
218
|
+
editor.addEventListener('change', (e) => {
|
|
219
|
+
console.log(e.detail);
|
|
220
|
+
// → { type: "FeatureCollection", features: [{ type: "Feature", ... }] }
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### `error`
|
|
225
|
+
|
|
226
|
+
Fired when content changes but JSON is invalid or GeoJSON validation fails (debounced 150ms).
|
|
227
|
+
|
|
228
|
+
```javascript
|
|
229
|
+
editor.addEventListener('error', (e) => {
|
|
230
|
+
console.error(e.detail.error); // Error message
|
|
231
|
+
console.log(e.detail.errors); // Array of validation errors (GeoJSON validation only)
|
|
232
|
+
console.log(e.detail.content); // Raw content for debugging
|
|
233
|
+
});
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**Event detail properties:**
|
|
237
|
+
|
|
238
|
+
| Property | Type | Description |
|
|
239
|
+
|----------|------|-------------|
|
|
240
|
+
| `error` | `string` | Error message (JSON parse error or GeoJSON validation summary) |
|
|
241
|
+
| `errors` | `string[]` | Array of validation errors with paths (GeoJSON validation only) |
|
|
242
|
+
| `content` | `string` | Raw editor content (for debugging) |
|
|
243
|
+
|
|
244
|
+
**GeoJSON validation errors include:**
|
|
245
|
+
- Invalid types (e.g., `"LinearRing"`)
|
|
246
|
+
- Unknown types (any `type` value not in the GeoJSON specification)
|
|
247
|
+
|
|
248
|
+
## Styling
|
|
249
|
+
|
|
250
|
+
The component uses Shadow DOM with CSS variables for theming. Themes can be customized via the `setTheme()` API.
|
|
251
|
+
|
|
252
|
+
## Browser Support
|
|
253
|
+
|
|
254
|
+
Works in all modern browsers supporting:
|
|
255
|
+
- Web Components
|
|
256
|
+
- Shadow DOM
|
|
257
|
+
- ES6 Modules
|
|
258
|
+
|
|
259
|
+
## Development
|
|
260
|
+
|
|
261
|
+
See [DEVELOPMENT.md](DEVELOPMENT.md) for detailed development guide.
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
# Install dependencies
|
|
265
|
+
npm install
|
|
266
|
+
|
|
267
|
+
# Start dev server with live demo
|
|
268
|
+
npm run dev
|
|
269
|
+
|
|
270
|
+
# Build for production
|
|
271
|
+
npm run build
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## License
|
|
275
|
+
|
|
276
|
+
MIT
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
var e=Object.defineProperty,t=(t,n,o)=>n in t?e(t,n,{enumerable:!0,configurable:!0,writable:!0,value:o}):t[n]=o,n=(e,n,o)=>t(e,"symbol"!=typeof n?n+"":n,o);
|
|
2
|
+
/**
|
|
3
|
+
* @license MIT
|
|
4
|
+
* @name @softwarity/geojson-editor
|
|
5
|
+
* @version 1.0.1
|
|
6
|
+
* @author Softwarity (https://www.softwarity.io/)
|
|
7
|
+
* @copyright 2024 Softwarity
|
|
8
|
+
* @see https://github.com/softwarity/geojson-editor
|
|
9
|
+
*/
|
|
10
|
+
const o=class e extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this.collapsedData=new Map,this.colorPositions=[],this.nodeTogglePositions=[],this.highlightTimer=null,this._cachedLineHeight=null,this._cachedPaddingTop=null,this.themes={dark:{...e.DEFAULT_THEMES.dark},light:{...e.DEFAULT_THEMES.light}}}static get observedAttributes(){return["readonly","value","placeholder","auto-format","dark-selector","feature-collection"]}connectedCallback(){this.render(),this.setupEventListeners(),this.updatePrefixSuffix(),this.updateThemeCSS(),this.value&&(this.updateHighlight(),requestAnimationFrame(()=>{this.applyAutoCollapsed()}))}attributeChangedCallback(e,t,n){var o;if(t!==n)if("value"===e)this.updateValue(n);else if("readonly"===e)this.updateReadonly();else if("placeholder"===e){const e=this.shadowRoot.querySelector("textarea");e&&(e.placeholder=n||"")}else if("dark-selector"===e)this.updateThemeCSS();else if("feature-collection"===e)this.updatePrefixSuffix();else if("auto-format"===e){const e=null==(o=this.shadowRoot)?void 0:o.getElementById("textarea");e&&e.value&&this.autoFormat&&(this.autoFormatContent(),this.updateHighlight())}}get readonly(){return this.hasAttribute("readonly")}get value(){return this.getAttribute("value")||""}get placeholder(){return this.getAttribute("placeholder")||""}get autoFormat(){return this.hasAttribute("auto-format")}get featureCollection(){return this.hasAttribute("feature-collection")}get prefix(){return this.featureCollection?e.FEATURE_COLLECTION_PREFIX:""}get suffix(){return this.featureCollection?e.FEATURE_COLLECTION_SUFFIX:""}render(){const e=`\n <div class="editor-prefix" id="editorPrefix"></div>\n <div class="editor-wrapper">\n <div class="gutter">\n <div class="gutter-content" id="gutterContent"></div>\n </div>\n <div class="editor-content">\n <div class="highlight-layer" id="highlightLayer"></div>\n <textarea\n id="textarea"\n spellcheck="false"\n autocomplete="off"\n autocorrect="off"\n autocapitalize="off"\n placeholder="${this.placeholder}"\n ></textarea>\n </div>\n </div>\n <div class="editor-suffix" id="editorSuffix"></div>\n `;this.shadowRoot.innerHTML="\n <style>\n /* Global reset with exact values to prevent external CSS interference */\n :host *,\n :host *::before,\n :host *::after {\n box-sizing: border-box;\n font-family: 'Courier New', Courier, monospace;\n font-size: 13px;\n font-weight: normal;\n font-style: normal;\n font-variant: normal;\n line-height: 1.5;\n letter-spacing: 0;\n text-transform: none;\n text-decoration: none;\n text-indent: 0;\n word-spacing: 0;\n }\n\n :host {\n display: flex;\n flex-direction: column;\n position: relative;\n width: 100%;\n height: 400px;\n font-family: 'Courier New', Courier, monospace;\n font-size: 13px;\n line-height: 1.5;\n border-radius: 4px;\n overflow: hidden;\n }\n\n :host([readonly]) .editor-wrapper::after {\n content: '';\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n pointer-events: none;\n background: repeating-linear-gradient(\n -45deg,\n rgba(128, 128, 128, 0.08),\n rgba(128, 128, 128, 0.08) 3px,\n transparent 3px,\n transparent 12px\n );\n z-index: 1;\n }\n\n :host([readonly]) textarea {\n cursor: text;\n }\n\n .editor-wrapper {\n position: relative;\n width: 100%;\n flex: 1;\n background: var(--bg-color);\n display: flex;\n font-family: 'Courier New', Courier, monospace;\n font-size: 13px;\n line-height: 1.5;\n }\n\n .gutter {\n width: 24px;\n height: 100%;\n background: var(--gutter-bg);\n border-right: 1px solid var(--gutter-border);\n overflow: hidden;\n flex-shrink: 0;\n position: relative;\n }\n\n .gutter-content {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n padding: 8px 4px;\n }\n\n .gutter-line {\n position: absolute;\n left: 0;\n width: 100%;\n height: 1.5em;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .color-indicator {\n width: 12px;\n height: 12px;\n border-radius: 2px;\n border: 1px solid #555;\n cursor: pointer;\n transition: transform 0.1s;\n flex-shrink: 0;\n }\n\n .color-indicator:hover {\n transform: scale(1.2);\n border-color: #fff;\n }\n\n .collapse-button {\n width: 12px;\n height: 12px;\n background: var(--collapse-btn-bg);\n border: 1px solid var(--collapse-btn-border);\n border-radius: 2px;\n color: var(--collapse-btn);\n font-size: 8px;\n font-weight: bold;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.1s;\n flex-shrink: 0;\n user-select: none;\n }\n\n .collapse-button:hover {\n background: var(--collapse-btn-bg);\n border-color: var(--collapse-btn);\n transform: scale(1.1);\n }\n\n .color-picker-popup {\n position: absolute;\n background: #2d2d30;\n border: 1px solid #555;\n border-radius: 4px;\n padding: 8px;\n z-index: 1000;\n box-shadow: 0 4px 12px rgba(0,0,0,0.5);\n }\n\n .color-picker-popup input[type=\"color\"] {\n width: 150px;\n height: 30px;\n border: none;\n cursor: pointer;\n }\n\n .editor-content {\n position: relative;\n flex: 1;\n overflow: hidden;\n }\n\n .highlight-layer {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n padding: 8px 12px;\n font-family: 'Courier New', Courier, monospace;\n font-size: 13px;\n font-weight: normal;\n font-style: normal;\n line-height: 1.5;\n white-space: pre-wrap;\n word-wrap: break-word;\n overflow: auto;\n pointer-events: none;\n z-index: 1;\n color: var(--text-color);\n }\n\n .highlight-layer::-webkit-scrollbar {\n display: none;\n }\n\n textarea {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n padding: 8px 12px;\n margin: 0;\n border: none;\n outline: none;\n background: transparent;\n color: transparent;\n caret-color: var(--caret-color);\n font-family: 'Courier New', Courier, monospace;\n font-size: 13px;\n font-weight: normal;\n font-style: normal;\n line-height: 1.5;\n white-space: pre-wrap;\n word-wrap: break-word;\n resize: none;\n overflow: auto;\n z-index: 2;\n box-sizing: border-box;\n }\n\n textarea::selection {\n background: rgba(51, 153, 255, 0.3);\n }\n\n textarea::placeholder {\n color: #6a6a6a;\n font-family: 'Courier New', Courier, monospace;\n font-size: 13px;\n font-weight: normal;\n font-style: normal;\n opacity: 1;\n }\n\n textarea:disabled {\n cursor: not-allowed;\n opacity: 0.6;\n }\n\n /* Syntax highlighting colors */\n .json-key {\n color: var(--json-key);\n }\n\n .json-string {\n color: var(--json-string);\n }\n\n .json-number {\n color: var(--json-number);\n }\n\n .json-boolean {\n color: var(--json-boolean);\n }\n\n .json-null {\n color: var(--json-null);\n }\n\n .json-punctuation {\n color: var(--json-punct);\n }\n\n /* GeoJSON-specific highlighting */\n .geojson-key {\n color: var(--geojson-key);\n font-weight: 600;\n }\n\n .geojson-type {\n color: var(--geojson-type);\n font-weight: 600;\n }\n\n .geojson-type-invalid {\n color: var(--geojson-type-invalid);\n font-weight: 600;\n }\n\n .json-key-invalid {\n color: var(--json-key-invalid);\n }\n\n /* Prefix and suffix styling */\n .editor-prefix,\n .editor-suffix {\n padding: 4px 12px;\n color: var(--text-color);\n background: var(--bg-color);\n user-select: none;\n white-space: pre-wrap;\n word-wrap: break-word;\n flex-shrink: 0;\n font-family: 'Courier New', Courier, monospace;\n font-size: 13px;\n line-height: 1.5;\n opacity: 0.6;\n border-left: 3px solid rgba(102, 126, 234, 0.5);\n }\n\n .editor-prefix {\n border-bottom: 1px solid rgba(255, 255, 255, 0.1);\n }\n\n .editor-suffix {\n border-top: 1px solid rgba(255, 255, 255, 0.1);\n }\n\n /* Scrollbar styling */\n textarea::-webkit-scrollbar {\n width: 10px;\n height: 10px;\n }\n\n textarea::-webkit-scrollbar-track {\n background: #1e1e1e;\n }\n\n textarea::-webkit-scrollbar-thumb {\n background: #424242;\n border-radius: 5px;\n }\n\n textarea::-webkit-scrollbar-thumb:hover {\n background: #4e4e4e;\n }\n </style>\n "+e}setupEventListeners(){const e=this.shadowRoot.getElementById("textarea"),t=this.shadowRoot.getElementById("highlightLayer");e.addEventListener("scroll",()=>{t.scrollTop=e.scrollTop,t.scrollLeft=e.scrollLeft,this.syncGutterScroll(e.scrollTop)}),e.addEventListener("input",()=>{clearTimeout(this.highlightTimer),this.highlightTimer=setTimeout(()=>{this.autoFormat&&this.autoFormatContentWithCursor(),this.updateHighlight(),this.emitChange()},150)}),e.addEventListener("paste",()=>{clearTimeout(this.highlightTimer),setTimeout(()=>{this.autoFormat&&this.autoFormatContentWithCursor(),this.updateHighlight(),this.emitChange()},10)}),this.shadowRoot.getElementById("gutterContent").addEventListener("click",e=>{if(e.target.classList.contains("color-indicator")){const t=parseInt(e.target.dataset.line),n=e.target.dataset.color,o=e.target.dataset.attributeName;this.showColorPicker(e.target,t,n,o)}else if(e.target.classList.contains("collapse-button")){const t=e.target.dataset.nodeKey,n=parseInt(e.target.dataset.line);this.toggleCollapse(t,n)}}),this.shadowRoot.querySelector(".gutter").addEventListener("wheel",t=>{t.preventDefault(),e.scrollTop+=t.deltaY}),e.addEventListener("keydown",e=>{this.handleKeydownInCollapsedArea(e)}),e.addEventListener("copy",e=>{this.handleCopyWithCollapsedContent(e)}),e.addEventListener("cut",e=>{this.handleCutWithCollapsedContent(e)}),this.updateReadonly()}syncGutterScroll(e){this.shadowRoot.getElementById("gutterContent").style.transform=`translateY(-${e}px)`}updateReadonly(){const e=this.shadowRoot.getElementById("textarea");e&&(e.disabled=this.readonly)}updateValue(e){const t=this.shadowRoot.getElementById("textarea");if(t&&t.value!==e){if(t.value=e||"",this.autoFormat&&e)try{const n=this.prefix,o=this.suffix,s=n.trimEnd().endsWith("["),i=o.trimStart().startsWith("]");if(s&&i){const n="["+e+"]",o=JSON.parse(n),s=JSON.stringify(o,null,2).split("\n");s.length>2?t.value=s.slice(1,-1).join("\n"):t.value=""}else if(!n&&!o){const n=JSON.parse(e);t.value=JSON.stringify(n,null,2)}}catch{}this.updateHighlight(),t.value&&requestAnimationFrame(()=>{this.applyAutoCollapsed()})}}updatePrefixSuffix(){const e=this.shadowRoot.getElementById("editorPrefix"),t=this.shadowRoot.getElementById("editorSuffix");e&&(this.prefix?(e.textContent=this.prefix,e.style.display="block"):(e.textContent="",e.style.display="none")),t&&(this.suffix?(t.textContent=this.suffix,t.style.display="block"):(t.textContent="",t.style.display="none"))}updateHighlight(){const e=this.shadowRoot.getElementById("textarea"),t=this.shadowRoot.getElementById("highlightLayer");if(!e||!t)return;const n=e.value,{highlighted:o,colors:s,toggles:i}=this.highlightJSON(n);t.innerHTML=o,this.colorPositions=s,this.nodeTogglePositions=i,this.updateGutter()}highlightJSON(t){if(!t.trim())return{highlighted:"",colors:[],toggles:[]};const n=t.split("\n"),o=[],s=[];let i=[];const a=this.buildContextMap(t);return n.forEach((t,n)=>{const r=e.REGEX;let l;for(r.colorInLine.lastIndex=0;null!==(l=r.colorInLine.exec(t));)o.push({line:n,color:l[2],attributeName:l[1]});const h=t.match(r.collapsibleNode);if(h){const e=h[2];t.includes("{...}")||t.includes("[...]")?s.push({line:n,nodeKey:e,isCollapsed:!0}):this.bracketClosesOnSameLine(t,h[3])||s.push({line:n,nodeKey:e,isCollapsed:!1})}const d=a.get(n);i.push(this.highlightSyntax(t,d))}),{highlighted:i.join("\n"),colors:o,toggles:s}}buildContextMap(t){var n;const o=t.split("\n"),s=new Map,i=[];let a=null;const r=this.featureCollection?"Feature":null;for(let t=0;t<o.length;t++){const l=o[t],h=i.length>0?null==(n=i[i.length-1])?void 0:n.context:r;s.set(t,h);for(let t=0;t<l.length;t++){const n=l[t];if('"'===n){const n=l.substring(t).match(/^"([^"]+)"\s*:/);if(n){const o=n[1];e.CONTEXT_CHANGING_KEYS[o]&&(a=e.CONTEXT_CHANGING_KEYS[o]),t+=n[0].length-1;continue}}if('"'===n&&i.length>0&&l.substring(0,t).match(/"type"\s*:\s*$/)){const n=l.substring(t).match(/^"([^"]+)"/);if(n&&e.GEOJSON_TYPES_ALL.includes(n[1])){const e=i[i.length-1];e&&(e.context=n[1])}}if("{"===n||"["===n){let e;if(a)e=a,a=null;else if(0===i.length)e=r;else{const t=i[i.length-1];e=t&&t.isArray?t.context:null}i.push({context:e,isArray:"["===n})}("}"===n||"]"===n)&&i.length>0&&i.pop()}}return s}highlightSyntax(t,n){if(!t.trim())return"";const o=n?e.VALID_KEYS_BY_CONTEXT[n]:null,s=e.REGEX;return t.replace(s.ampersand,"&").replace(s.lessThan,"<").replace(s.greaterThan,">").replace(s.jsonKey,(t,s)=>"properties"===n?`<span class="json-key">"${s}"</span>:`:e.GEOJSON_STRUCTURAL_KEYS.includes(s)?`<span class="geojson-key">"${s}"</span>:`:(t=>!(!e.GEOJSON_STRUCTURAL_KEYS.includes(t)&&n&&null!=o)||o.includes(t))(s)?`<span class="json-key">"${s}"</span>:`:`<span class="json-key-invalid">"${s}"</span>:`).replace(s.typeValue,(t,o)=>(t=>!n||"properties"===n||("geometry"===n||e.GEOJSON_TYPES_GEOMETRY.includes(n)?e.GEOJSON_TYPES_GEOMETRY.includes(t):"Feature"!==n&&"FeatureCollection"!==n||e.GEOJSON_TYPES_ALL.includes(t)))(o)?`<span class="geojson-key">"type"</span>: <span class="geojson-type">"${o}"</span>`:`<span class="geojson-key">"type"</span>: <span class="geojson-type-invalid">"${o}"</span>`).replace(s.stringValue,(e,t)=>e.includes("<span")?e:`: <span class="json-string">"${t}"</span>`).replace(s.numberAfterColon,': <span class="json-number">$1</span>').replace(s.boolean,': <span class="json-boolean">$1</span>').replace(s.nullValue,': <span class="json-null">$1</span>').replace(s.allNumbers,'<span class="json-number">$1</span>').replace(s.punctuation,'<span class="json-punctuation">$1</span>')}toggleCollapse(e,t){const n=this.shadowRoot.getElementById("textarea"),o=n.value.split("\n"),s=o[t];if(s.includes("{...}")||s.includes("[...]")){let n=null,i=null;const a=`${t}-${e}`;if(this.collapsedData.has(a))n=a,i=this.collapsedData.get(a);else for(const[t,o]of this.collapsedData.entries())if(o.nodeKey===e){const e=s.match(/^(\s*)/)[1].length;if(o.indent===e){n=t,i=o;break}}if(!n||!i)return;const{originalLine:r,content:l}=i;o[t]=r,o.splice(t+1,0,...l),this.collapsedData.delete(n)}else{const n=s.match(/^(\s*)"([^"]+)"\s*:\s*([{\[])/);if(!n)return;const i=n[1],a=n[3],r="{"===a?"}":"]";if(this.bracketClosesOnSameLine(s,a))return;let l=1,h=t;const d=[];for(let e=t+1;e<o.length;e++){const t=o[e];for(const e of t)e===a&&l++,e===r&&l--;if(d.push(t),0===l){h=e;break}}const c=`${t}-${e}`;this.collapsedData.set(c,{originalLine:s,content:d,indent:i.length,nodeKey:e});const p=s.substring(0,s.indexOf(a)),u=o[h]&&o[h].trim().endsWith(",");o[t]=`${p}${a}...${r}${u?",":""}`,o.splice(t+1,h-t)}n.value=o.join("\n"),this.updateHighlight()}applyAutoCollapsed(){const e=this.shadowRoot.getElementById("textarea");if(!e||!e.value)return;const t=e.value.split("\n");for(let e=t.length-1;e>=0;e--){const n=t[e],o=n.match(/^(\s*)"(\w+)"\s*:\s*([{\[])/);if(o){const s=o[2];if("coordinates"===s){const i=o[1],a=o[3],r="{"===a?"}":"]";if(this.bracketClosesOnSameLine(n,a))continue;let l=1,h=e;const d=[];for(let n=e+1;n<t.length;n++){const e=t[n];for(const t of e)t===a&&l++,t===r&&l--;if(d.push(e),0===l){h=n;break}}const c=`${e}-${s}`;this.collapsedData.set(c,{originalLine:n,content:d,indent:i.length,nodeKey:s});const p=n.substring(0,n.indexOf(a)),u=t[h]&&t[h].trim().endsWith(",");t[e]=`${p}${a}...${r}${u?",":""}`,t.splice(e+1,h-e)}}}e.value=t.join("\n"),this.updateHighlight()}updateGutter(){const e=this.shadowRoot.getElementById("gutterContent"),t=this.shadowRoot.getElementById("textarea");if(!t)return;if(null===this._cachedLineHeight){const e=getComputedStyle(t);this._cachedLineHeight=parseFloat(e.lineHeight),this._cachedPaddingTop=parseFloat(e.paddingTop)}const n=this._cachedLineHeight,o=this._cachedPaddingTop;e.textContent="";const s=new Map;this.colorPositions.forEach(({line:e,color:t,attributeName:n})=>{s.has(e)||s.set(e,{colors:[],buttons:[]}),s.get(e).colors.push({color:t,attributeName:n})}),this.nodeTogglePositions.forEach(({line:e,nodeKey:t,isCollapsed:n})=>{s.has(e)||s.set(e,{colors:[],buttons:[]}),s.get(e).buttons.push({nodeKey:t,isCollapsed:n})});const i=document.createDocumentFragment();s.forEach((e,t)=>{const s=document.createElement("div");s.className="gutter-line",s.style.top=`${o+t*n}px`,e.colors.forEach(({color:e,attributeName:n})=>{const o=document.createElement("div");o.className="color-indicator",o.style.backgroundColor=e,o.dataset.line=t,o.dataset.color=e,o.dataset.attributeName=n,o.title=`${n}: ${e}`,s.appendChild(o)}),e.buttons.forEach(({nodeKey:e,isCollapsed:n})=>{const o=document.createElement("div");o.className="collapse-button",o.textContent=n?"+":"-",o.dataset.line=t,o.dataset.nodeKey=e,o.title=n?"Expand":"Collapse",s.appendChild(o)}),i.appendChild(s)}),e.appendChild(i)}showColorPicker(e,t,n,o){const s=document.querySelector(".geojson-color-picker-input");s&&s.remove();const i=document.createElement("input");i.type="color",i.value=n,i.className="geojson-color-picker-input";const a=e.getBoundingClientRect();i.style.position="fixed",i.style.left=`${a.left}px`,i.style.top=`${a.top}px`,i.style.width="12px",i.style.height="12px",i.style.opacity="0.01",i.style.border="none",i.style.padding="0",i.style.zIndex="9999",i.addEventListener("input",e=>{this.updateColorValue(t,e.target.value,o)}),i.addEventListener("change",e=>{this.updateColorValue(t,e.target.value,o)});const r=e=>{e.target!==i&&!i.contains(e.target)&&(i.remove(),document.removeEventListener("click",r,!0))};document.body.appendChild(i),setTimeout(()=>{document.addEventListener("click",r,!0)},100),i.focus(),i.click()}updateColorValue(e,t,n){const o=this.shadowRoot.getElementById("textarea"),s=o.value.split("\n"),i=new RegExp(`"${n}"\\s*:\\s*"#[0-9a-fA-F]{6}"`);s[e]=s[e].replace(i,`"${n}": "${t}"`),o.value=s.join("\n"),this.updateHighlight(),this.emitChange()}handleKeydownInCollapsedArea(e){if(["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Home","End","PageUp","PageDown","Tab"].includes(e.key)||e.ctrlKey||e.metaKey)return;const t=this.shadowRoot.getElementById("textarea"),n=t.selectionStart,o=t.value.substring(0,n).split("\n").length-1,s=t.value.split("\n")[o];s&&(s.includes("{...}")||s.includes("[...]"))&&e.preventDefault()}handleCopyWithCollapsedContent(e){const t=this.shadowRoot.getElementById("textarea"),n=t.selectionStart,o=t.selectionEnd;if(n===o)return;const s=t.value.substring(n,o);if(!s.includes("{...}")&&!s.includes("[...]"))return;const i=this.expandCollapsedMarkersInText(s,n);e.preventDefault(),e.clipboardData.setData("text/plain",i)}expandCollapsedMarkersInText(e,t){const n=this.shadowRoot.getElementById("textarea").value.substring(0,t).split("\n").length-1,o=e.split("\n"),s=[];return o.forEach((e,t)=>{const o=n+t;if(e.includes("{...}")||e.includes("[...]")){let t=!1;this.collapsedData.forEach((e,n)=>{parseInt(n.split("-")[0])===o&&(s.push(e.originalLine),s.push(...e.content),t=!0)}),t||s.push(e)}else s.push(e)}),s.join("\n")}handleCutWithCollapsedContent(e){this.handleCopyWithCollapsedContent(e);const t=this.shadowRoot.getElementById("textarea"),n=t.selectionStart,o=t.selectionEnd;if(n!==o){const e=t.value;t.value=e.substring(0,n)+e.substring(o),t.selectionStart=t.selectionEnd=n,this.updateHighlight(),this.emitChange()}}emitChange(){const e=this.shadowRoot.getElementById("textarea"),t=this.expandAllCollapsed(e.value),n=this.prefix+t+this.suffix;try{const e=JSON.parse(n),o=this.validateGeoJSON(e);o.length>0?this.dispatchEvent(new CustomEvent("error",{detail:{timestamp:(new Date).toISOString(),error:`GeoJSON validation: ${o.join("; ")}`,errors:o,content:t},bubbles:!0,composed:!0})):this.dispatchEvent(new CustomEvent("change",{detail:e,bubbles:!0,composed:!0}))}catch(e){this.dispatchEvent(new CustomEvent("error",{detail:{timestamp:(new Date).toISOString(),error:e.message,content:t},bubbles:!0,composed:!0}))}}validateGeoJSON(t,n="",o="root"){const s=[];if(!t||"object"!=typeof t)return s;if("properties"!==o&&void 0!==t.type){const i=t.type;"string"==typeof i&&("geometry"===o?e.GEOJSON_TYPES_GEOMETRY.includes(i)||s.push(`Invalid geometry type "${i}" at ${n||"root"} (expected: ${e.GEOJSON_TYPES_GEOMETRY.join(", ")})`):e.GEOJSON_TYPES_FEATURE.includes(i)||s.push(`Invalid type "${i}" at ${n||"root"} (expected: ${e.GEOJSON_TYPES_FEATURE.join(", ")})`))}if(Array.isArray(t))t.forEach((e,t)=>{s.push(...this.validateGeoJSON(e,`${n}[${t}]`,o))});else for(const[e,i]of Object.entries(t))if("object"==typeof i&&null!==i){const t=n?`${n}.${e}`:e;let a=o;"properties"===e?a="properties":"geometry"===e||"geometries"===e?a="geometry":"features"===e&&(a="root"),s.push(...this.validateGeoJSON(i,t,a))}return s}bracketClosesOnSameLine(e,t){const n="{"===t?"}":"]",o=e.indexOf(t);if(-1===o)return!1;const s=e.substring(o+1);let i=1;for(const e of s)if(e===t&&i++,e===n&&i--,0===i)return!0;return!1}expandAllCollapsed(t){const n=e.REGEX;for(;t.includes("{...}")||t.includes("[...]");){const e=t.split("\n");let o=!1;for(let t=0;t<e.length;t++){const s=e[t];if(!s.includes("{...}")&&!s.includes("[...]"))continue;const i=s.match(n.collapsedMarker);if(!i)continue;const a=i[2],r=i[1].length,l=`${t}-${a}`;let h=this.collapsedData.has(l)?l:null;if(!h)for(const[e,t]of this.collapsedData.entries())if(t.nodeKey===a&&t.indent===r){h=e;break}if(h){const{originalLine:n,content:s}=this.collapsedData.get(h);e[t]=n,e.splice(t+1,0,...s),o=!0;break}}if(!o)break;t=e.join("\n")}return t}formatJSONContent(e){const t=this.prefix,n=this.suffix,o=t.trimEnd().endsWith("["),s=n.trimStart().startsWith("]");if(o&&s){const t="["+e+"]",n=JSON.parse(t),o=JSON.stringify(n,null,2).split("\n");return o.length>2?o.slice(1,-1).join("\n"):""}if(t||n){const o=t+e+n;return JSON.parse(o),e}{const t=JSON.parse(e);return JSON.stringify(t,null,2)}}autoFormatContentWithCursor(){const e=this.shadowRoot.getElementById("textarea"),t=e.selectionStart,n=e.value.substring(0,t).split("\n"),o=n.length-1,s=n[n.length-1].length,i=Array.from(this.collapsedData.values()).map(e=>({nodeKey:e.nodeKey,indent:e.indent})),a=this.expandAllCollapsed(e.value);try{const t=this.formatJSONContent(a);if(t!==a){this.collapsedData.clear(),e.value=t,i.length>0&&this.reapplyCollapsed(i);const n=e.value.split("\n");if(o<n.length){const t=Math.min(s,n[o].length);let i=0;for(let e=0;e<o;e++)i+=n[e].length+1;i+=t,e.setSelectionRange(i,i)}}}catch{}}autoFormatContent(){const e=this.shadowRoot.getElementById("textarea"),t=Array.from(this.collapsedData.values()).map(e=>({nodeKey:e.nodeKey,indent:e.indent})),n=this.expandAllCollapsed(e.value);try{const o=this.formatJSONContent(n);o!==n&&(this.collapsedData.clear(),e.value=o,t.length>0&&this.reapplyCollapsed(t))}catch{}}reapplyCollapsed(e){const t=this.shadowRoot.getElementById("textarea"),n=t.value.split("\n"),o=new Map;e.forEach(({nodeKey:e,indent:t})=>{const n=`${e}-${t}`;o.set(n,(o.get(n)||0)+1)});const s=new Map;for(let e=n.length-1;e>=0;e--){const t=n[e],i=t.match(/^(\s*)"(\w+)"\s*:\s*([{\[])/);if(i){const a=i[2],r=`${a}-${i[1].length}`;if(o.has(r)&&(s.set(r,(s.get(r)||0)+1),s.get(r)<=o.get(r))){const o=i[1],s=i[3],r="{"===s?"}":"]";if(this.bracketClosesOnSameLine(t,s))continue;let l=1,h=e;const d=[];for(let t=e+1;t<n.length;t++){const e=n[t];for(const t of e)t===s&&l++,t===r&&l--;if(d.push(e),0===l){h=t;break}}const c=`${e}-${a}`;this.collapsedData.set(c,{originalLine:t,content:d,indent:o.length,nodeKey:a});const p=t.substring(0,t.indexOf(s)),u=n[h]&&n[h].trim().endsWith(",");n[e]=`${p}${s}...${r}${u?",":""}`,n.splice(e+1,h-e)}}}t.value=n.join("\n")}parseSelectorToHostRule(e){return e&&""!==e?e.startsWith(".")&&!e.includes(" ")?`:host(${e})`:`:host-context(${e})`:':host([data-color-scheme="dark"])'}updateThemeCSS(){const e=this.getAttribute("dark-selector")||".dark",t=this.parseSelectorToHostRule(e);let n=this.shadowRoot.getElementById("theme-styles");n||(n=document.createElement("style"),n.id="theme-styles",this.shadowRoot.insertBefore(n,this.shadowRoot.firstChild));const o=`\n :host {\n --bg-color: ${this.themes.light.background};\n --text-color: ${this.themes.light.textColor};\n --caret-color: ${this.themes.light.caretColor};\n --gutter-bg: ${this.themes.light.gutterBackground};\n --gutter-border: ${this.themes.light.gutterBorder};\n --json-key: ${this.themes.light.jsonKey};\n --json-string: ${this.themes.light.jsonString};\n --json-number: ${this.themes.light.jsonNumber};\n --json-boolean: ${this.themes.light.jsonBoolean};\n --json-null: ${this.themes.light.jsonNull};\n --json-punct: ${this.themes.light.jsonPunctuation};\n --collapse-btn: ${this.themes.light.collapseButton};\n --collapse-btn-bg: ${this.themes.light.collapseButtonBg};\n --collapse-btn-border: ${this.themes.light.collapseButtonBorder};\n --geojson-key: ${this.themes.light.geojsonKey};\n --geojson-type: ${this.themes.light.geojsonType};\n --geojson-type-invalid: ${this.themes.light.geojsonTypeInvalid};\n --json-key-invalid: ${this.themes.light.jsonKeyInvalid};\n }\n\n ${t} {\n --bg-color: ${this.themes.dark.background};\n --text-color: ${this.themes.dark.textColor};\n --caret-color: ${this.themes.dark.caretColor};\n --gutter-bg: ${this.themes.dark.gutterBackground};\n --gutter-border: ${this.themes.dark.gutterBorder};\n --json-key: ${this.themes.dark.jsonKey};\n --json-string: ${this.themes.dark.jsonString};\n --json-number: ${this.themes.dark.jsonNumber};\n --json-boolean: ${this.themes.dark.jsonBoolean};\n --json-null: ${this.themes.dark.jsonNull};\n --json-punct: ${this.themes.dark.jsonPunctuation};\n --collapse-btn: ${this.themes.dark.collapseButton};\n --collapse-btn-bg: ${this.themes.dark.collapseButtonBg};\n --collapse-btn-border: ${this.themes.dark.collapseButtonBorder};\n --geojson-key: ${this.themes.dark.geojsonKey};\n --geojson-type: ${this.themes.dark.geojsonType};\n --geojson-type-invalid: ${this.themes.dark.geojsonTypeInvalid};\n --json-key-invalid: ${this.themes.dark.jsonKeyInvalid};\n }\n `;n.textContent=o}getTheme(){return{dark:{...this.themes.dark},light:{...this.themes.light}}}setTheme(e){e.dark&&(this.themes.dark={...this.themes.dark,...e.dark}),e.light&&(this.themes.light={...this.themes.light,...e.light}),this.updateThemeCSS()}resetTheme(){this.themes={dark:{...e.DEFAULT_THEMES.dark},light:{...e.DEFAULT_THEMES.light}},this.updateThemeCSS()}};n(o,"DEFAULT_THEMES",{dark:{background:"#1e1e1e",textColor:"#d4d4d4",caretColor:"#fff",gutterBackground:"#252526",gutterBorder:"#3e3e42",jsonKey:"#9cdcfe",jsonString:"#ce9178",jsonNumber:"#b5cea8",jsonBoolean:"#569cd6",jsonNull:"#569cd6",jsonPunctuation:"#d4d4d4",collapseButton:"#c586c0",collapseButtonBg:"#3e3e42",collapseButtonBorder:"#555",geojsonKey:"#c586c0",geojsonType:"#4ec9b0",geojsonTypeInvalid:"#f44747",jsonKeyInvalid:"#f44747"},light:{background:"#ffffff",textColor:"#333333",caretColor:"#000",gutterBackground:"#f5f5f5",gutterBorder:"#ddd",jsonKey:"#0000ff",jsonString:"#a31515",jsonNumber:"#098658",jsonBoolean:"#0000ff",jsonNull:"#0000ff",jsonPunctuation:"#333333",collapseButton:"#a31515",collapseButtonBg:"#e0e0e0",collapseButtonBorder:"#999",geojsonKey:"#af00db",geojsonType:"#267f99",geojsonTypeInvalid:"#d32f2f",jsonKeyInvalid:"#d32f2f"}}),n(o,"FEATURE_COLLECTION_PREFIX",'{"type": "FeatureCollection", "features": ['),n(o,"FEATURE_COLLECTION_SUFFIX","]}"),n(o,"REGEX",{ampersand:/&/g,lessThan:/</g,greaterThan:/>/g,jsonKey:/"([^"]+)"\s*:/g,typeValue:/<span class="geojson-key">"type"<\/span>:\s*"([^"]*)"/g,stringValue:/:\s*"([^"]*)"/g,numberAfterColon:/:\s*(-?\d+\.?\d*)/g,boolean:/:\s*(true|false)/g,nullValue:/:\s*(null)/g,allNumbers:/\b(-?\d+\.?\d*)\b/g,punctuation:/([{}[\],])/g,colorInLine:/"(\w+)"\s*:\s*"(#[0-9a-fA-F]{6})"/g,collapsibleNode:/^(\s*)"(\w+)"\s*:\s*([{\[])/,collapsedMarker:/^(\s*)"(\w+)"\s*:\s*([{\[])\.\.\.([\]\}])/}),n(o,"GEOJSON_TYPES_FEATURE",["Feature","FeatureCollection"]),n(o,"GEOJSON_TYPES_GEOMETRY",["Point","MultiPoint","LineString","MultiLineString","Polygon","MultiPolygon","GeometryCollection"]),n(o,"GEOJSON_TYPES_ALL",[...o.GEOJSON_TYPES_FEATURE,...o.GEOJSON_TYPES_GEOMETRY]),n(o,"VALID_KEYS_BY_CONTEXT",{Feature:["type","geometry","properties","id","bbox"],FeatureCollection:["type","features","bbox","properties"],Point:["type","coordinates","bbox"],MultiPoint:["type","coordinates","bbox"],LineString:["type","coordinates","bbox"],MultiLineString:["type","coordinates","bbox"],Polygon:["type","coordinates","bbox"],MultiPolygon:["type","coordinates","bbox"],GeometryCollection:["type","geometries","bbox"],properties:null,geometry:["type","coordinates","geometries","bbox"]}),n(o,"CONTEXT_CHANGING_KEYS",{geometry:"geometry",properties:"properties",features:"Feature",geometries:"geometry"}),n(o,"GEOJSON_STRUCTURAL_KEYS",["type","geometry","properties","features","geometries","coordinates","bbox","id","crs"]);let s=o;customElements.define("geojson-editor",s);
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@softwarity/geojson-editor",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "A feature-rich GeoJSON editor Web Component with syntax highlighting, collapsible nodes, and color picker",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/geojson-editor.js",
|
|
7
|
+
"module": "./dist/geojson-editor.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/geojson-editor.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"src"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"dev": "vite",
|
|
19
|
+
"build": "vite build && terser dist/geojson-editor.js -o dist/geojson-editor.js --compress passes=3 --mangle toplevel=true --comments /license/i",
|
|
20
|
+
"preview": "vite preview"
|
|
21
|
+
},
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/softwarity/geojson-editor.git"
|
|
25
|
+
},
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/softwarity/geojson-editor/issues"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://softwarity.github.io/geojson-editor/",
|
|
30
|
+
"keywords": [
|
|
31
|
+
"geojson",
|
|
32
|
+
"editor",
|
|
33
|
+
"web-component",
|
|
34
|
+
"custom-element",
|
|
35
|
+
"syntax-highlighting",
|
|
36
|
+
"json-editor"
|
|
37
|
+
],
|
|
38
|
+
"author": "Softwarity",
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"terser": "^5.44.1",
|
|
42
|
+
"vite": "^5.0.0"
|
|
43
|
+
}
|
|
44
|
+
}
|