@softwarity/geojson-editor 1.0.9 → 1.0.11

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/README.md CHANGED
@@ -25,6 +25,12 @@ A feature-rich, framework-agnostic **Web Component** for editing GeoJSON feature
25
25
 
26
26
  **[🚀 Try the Live Demo](https://softwarity.github.io/geojson-editor/)**
27
27
 
28
+ <p align="center">
29
+ <a href="https://softwarity.github.io/geojson-editor/">
30
+ <img src="preview.png" alt="GeoJSON Editor Preview" width="800">
31
+ </a>
32
+ </p>
33
+
28
34
  ## Why not Monaco, CodeMirror, or Prism?
29
35
 
30
36
  | | @softwarity/geojson-editor | Monaco Editor | CodeMirror 6 | Prism.js |
@@ -53,7 +59,8 @@ A feature-rich, framework-agnostic **Web Component** for editing GeoJSON feature
53
59
  - **GeoJSON-Aware Highlighting** - Distinct colors for GeoJSON keywords (`type`, `coordinates`, `geometry`, etc.)
54
60
  - **GeoJSON Type Validation** - Valid types (`Point`, `LineString`, `Polygon`, etc.) highlighted distinctly; invalid types (`LinearRing`, unknown types) shown with error styling (colors configurable via theme)
55
61
  - **Syntax Highlighting** - JSON syntax highlighting with customizable color schemes
56
- - **Collapsible Nodes** - Collapse/expand JSON objects and arrays with visual indicators (`{...}` / `[...]`); `coordinates` auto-collapsed on load
62
+ - **Collapsible Nodes** - Collapse/expand JSON objects and arrays with visual indicators (`{...}` / `[...]`); use Tab to expand and Shift+Tab to collapse; `coordinates` auto-collapsed on load
63
+ - **Virtualized Rendering** - Monaco-like architecture: only visible lines are rendered to DOM for optimal performance with large GeoJSON files
57
64
  - **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)
58
65
  - **Color Picker** - Built-in color picker for hex color properties (`#rrggbb`) in left gutter; click to open native color picker
59
66
  - **Boolean Checkbox** - Inline checkbox for boolean properties in left gutter; toggle to switch between `true`/`false` and emit changes (e.g., `marker: true` to show vertices)
@@ -154,9 +161,8 @@ editor.addEventListener('error', (e) => {
154
161
  | `placeholder` | `string` | `""` | Placeholder text |
155
162
  | `readonly` | `boolean` | `false` | Make editor read-only |
156
163
  | `dark-selector` | `string` | `".dark"` | CSS selector for dark theme (if matches → dark, else → light) |
157
- | `default-properties` | `string` | `""` | Default properties to add to features (see [Default Properties](#default-properties)) |
158
164
 
159
- **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.
165
+ **Note:** `coordinates` nodes are automatically collapsed when content is loaded to improve readability. Use Tab to expand and Shift+Tab to collapse nodes, or click the gutter toggle.
160
166
 
161
167
  ### Dark Selector Syntax
162
168
 
@@ -169,66 +175,6 @@ The `dark-selector` attribute determines when the dark theme is active. If the s
169
175
  - `html[data-bs-theme=dark]` - HTML has Bootstrap theme attribute: `<html data-bs-theme="dark">`
170
176
  - Empty string `""` - Uses component's `data-color-scheme` attribute as fallback
171
177
 
172
- ### Default Properties
173
-
174
- The `default-properties` attribute allows you to define default properties that will be automatically added to features. This is useful for setting visualization attributes (fill color, stroke color, opacity, etc.) that your mapping framework can use for styling.
175
-
176
- **Behavior:**
177
- - Default properties are **injected directly into the editor content** when features are added (via API or paste/typing)
178
- - Properties are only added if not already defined on the feature (existing properties are never overwritten)
179
- - Users can see and modify the default values in the editor
180
-
181
- #### Simple Format (all features)
182
-
183
- Apply the same default properties to all features:
184
-
185
- ```html
186
- <geojson-editor default-properties='{"fill-color": "#1a465b", "stroke-color": "#000", "stroke-width": 2}'></geojson-editor>
187
- ```
188
-
189
- #### Conditional Format (based on geometry type or properties)
190
-
191
- Apply different default properties based on conditions using an array of rules. Conditions support dot notation for nested properties:
192
-
193
- ```html
194
- <geojson-editor default-properties='[
195
- {"match": {"geometry.type": "Polygon"}, "values": {"fill-color": "#1a465b", "fill-opacity": 0.5}},
196
- {"match": {"geometry.type": "LineString"}, "values": {"stroke-color": "#ff0000", "stroke-width": 3}},
197
- {"match": {"geometry.type": "Point"}, "values": {"marker-color": "#00ff00"}}
198
- ]'></geojson-editor>
199
- ```
200
-
201
- #### Conditional on Feature Properties
202
-
203
- You can also match on existing feature properties:
204
-
205
- ```html
206
- <geojson-editor default-properties='[
207
- {"match": {"properties.type": "airport"}, "values": {"marker-symbol": "airport", "marker-color": "#0000ff"}},
208
- {"match": {"properties.category": "water"}, "values": {"fill-color": "#0066cc"}},
209
- {"values": {"stroke-width": 1}}
210
- ]'></geojson-editor>
211
- ```
212
-
213
- #### Combined: Conditionals with Fallback
214
-
215
- Rules without a `match` condition apply to all features (use as fallback). All matching rules are applied, with later rules taking precedence for the same property:
216
-
217
- ```html
218
- <geojson-editor default-properties='[
219
- {"values": {"stroke-width": 1, "stroke-color": "#333"}},
220
- {"match": {"geometry.type": "Polygon"}, "values": {"fill-color": "#1a465b", "fill-opacity": 0.3}},
221
- {"match": {"properties.highlighted": true}, "values": {"stroke-color": "#ff0000", "stroke-width": 3}}
222
- ]'></geojson-editor>
223
- ```
224
-
225
- In this example:
226
- - All features get `stroke-width: 1` and `stroke-color: "#333"` by default
227
- - Polygons additionally get fill styling
228
- - Features with `properties.highlighted: true` override the stroke styling
229
-
230
- **Use Case:** This feature is designed to work seamlessly with mapping libraries like Mapbox GL, Leaflet, OpenLayers, etc. You can define default visualization properties that your layer styling can reference, without manually editing each feature.
231
-
232
178
  ## API Methods
233
179
 
234
180
  ```javascript
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * @license MIT
3
3
  * @name @softwarity/geojson-editor
4
- * @version 1.0.9
4
+ * @version 1.0.11
5
5
  * @author Softwarity (https://www.softwarity.io/)
6
6
  * @copyright 2025 Softwarity
7
7
  * @see https://github.com/softwarity/geojson-editor
8
8
  */
9
- class e extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this.collapsedData=/* @__PURE__ */new Map,this.colorPositions=[],this.booleanPositions=[],this.nodeTogglePositions=[],this.hiddenFeatures=/* @__PURE__ */new Set,this.featureRanges=/* @__PURE__ */new Map,this.highlightTimer=null,this._cachedLineHeight=null,this._cachedPaddingTop=null,this.themes={dark:{},light:{}}}static get observedAttributes(){return["readonly","value","placeholder","dark-selector","default-properties"]}_defaultPropertiesRules=null;static _toKebabCase(e){return e.replace(/([A-Z])/g,"-$1").toLowerCase()}static DARK_THEME_DEFAULTS={bgColor:"#2b2b2b",textColor:"#a9b7c6",caretColor:"#bbbbbb",gutterBg:"#313335",gutterBorder:"#3c3f41",jsonKey:"#9876aa",jsonString:"#6a8759",jsonNumber:"#6897bb",jsonBoolean:"#cc7832",jsonNull:"#cc7832",jsonPunct:"#a9b7c6",controlColor:"#cc7832",controlBg:"#3c3f41",controlBorder:"#5a5a5a",geojsonKey:"#9876aa",geojsonType:"#6a8759",geojsonTypeInvalid:"#ff6b68",jsonKeyInvalid:"#ff6b68"};static 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]{3}|[0-9a-fA-F]{6}))"/g,booleanInLine:/"([\w-]+)"\s*:\s*(true|false)/g,collapsibleNode:/^(\s*)"(\w+)"\s*:\s*([{\[])/,collapsedMarker:/^(\s*)"(\w+)"\s*:\s*([{\[])\.\.\.([\]\}])/};static ICONS={expanded:"⌄",collapsed:"›",visibility:"👁"};_findCollapsedData(e,t,n){const o=`${e}-${t}`;if(this.collapsedData.has(o))return{key:o,data:this.collapsedData.get(o)};for(const[s,r]of this.collapsedData.entries())if(r.nodeKey===t&&r.indent===n)return{key:s,data:r};return null}connectedCallback(){this.render(),this.setupEventListeners(),this.updatePrefixSuffix(),this.updateThemeCSS(),this._parseDefaultProperties(),this.value&&this.updateValue(this.value),this.updatePlaceholderContent()}disconnectedCallback(){const e=document.querySelector(".geojson-color-picker-input");e&&e._closeListener&&(document.removeEventListener("click",e._closeListener,!0),e.remove()),this.highlightTimer&&(clearTimeout(this.highlightTimer),this.highlightTimer=null)}attributeChangedCallback(e,t,n){t!==n&&("value"===e?this.updateValue(n):"readonly"===e?this.updateReadonly():"placeholder"===e?this.updatePlaceholderContent():"dark-selector"===e?this.updateThemeCSS():"default-properties"===e&&this._parseDefaultProperties())}get readonly(){return this.hasAttribute("readonly")}get value(){return this.getAttribute("value")||""}get placeholder(){return this.getAttribute("placeholder")||""}get prefix(){return'{"type": "FeatureCollection", "features": ['}get suffix(){return"]}"}get defaultProperties(){return this.getAttribute("default-properties")||""}_parseDefaultProperties(){const e=this.defaultProperties;if(!e)return this._defaultPropertiesRules=[],this._defaultPropertiesRules;try{const t=JSON.parse(e);Array.isArray(t)?this._defaultPropertiesRules=t.map(e=>({match:e.match||null,values:e.values||{}})):this._defaultPropertiesRules="object"==typeof t&&null!==t?[{match:null,values:t}]:[]}catch(t){console.warn("geojson-editor: Invalid default-properties JSON:",t.message),this._defaultPropertiesRules=[]}return this._defaultPropertiesRules}_matchesCondition(e,t){if(!t||"object"!=typeof t)return!0;for(const[n,o]of Object.entries(t))if(this._getNestedValue(e,n)!==o)return!1;return!0}_getNestedValue(e,t){const n=t.split(".");let o=e;for(const s of n){if(null==o)return;o=o[s]}return o}_applyDefaultPropertiesToFeature(e){if(!e||"object"!=typeof e)return e;if(!this._defaultPropertiesRules||0===this._defaultPropertiesRules.length)return e;const t={};for(const r of this._defaultPropertiesRules)this._matchesCondition(e,r.match)&&Object.assign(t,r.values);if(0===Object.keys(t).length)return e;const n=e.properties||{},o={...n};let s=!1;for(const[r,a]of Object.entries(t))r in n||(o[r]=a,s=!0);return s?{...e,properties:o}:e}render(){const e=`\n <div class="prefix-wrapper">\n <div class="prefix-gutter"></div>\n <div class="editor-prefix" id="editorPrefix"></div>\n </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="placeholder-layer" id="placeholderLayer">${this.escapeHtml(this.placeholder)}</div>\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 ></textarea>\n </div>\n </div>\n <div class="suffix-wrapper">\n <div class="suffix-gutter"></div>\n <div class="editor-suffix" id="editorSuffix"></div>\n <button class="clear-btn" id="clearBtn" title="Clear editor">✕</button>\n </div>\n `;this.shadowRoot.innerHTML="\n <style>\n /* Base reset - protect against inherited styles */\n :host *, :host *::before, :host *::after {\n box-sizing: border-box;\n font: normal normal 13px/1.5 'Courier New', Courier, monospace;\n font-variant: normal;\n letter-spacing: 0;\n word-spacing: 0;\n text-transform: none;\n text-decoration: none;\n text-indent: 0;\n }\n\n :host {\n display: flex;\n flex-direction: column;\n position: relative;\n width: 100%;\n height: 400px;\n border-radius: 4px;\n overflow: hidden;\n }\n\n :host([readonly]) .editor-wrapper::after {\n content: '';\n position: absolute;\n inset: 0;\n pointer-events: none;\n background: repeating-linear-gradient(-45deg, rgba(128,128,128,0.08), rgba(128,128,128,0.08) 3px, transparent 3px, transparent 12px);\n z-index: 1;\n }\n\n :host([readonly]) textarea { cursor: text; }\n\n .editor-wrapper {\n position: relative;\n width: 100%;\n flex: 1;\n background: var(--bg-color, #fff);\n display: flex;\n }\n\n .gutter {\n width: 24px;\n height: 100%;\n background: var(--gutter-bg, #f0f0f0);\n border-right: 1px solid var(--gutter-border, #e0e0e0);\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, .collapse-button, .boolean-checkbox {\n width: 12px;\n height: 12px;\n border-radius: 2px;\n cursor: pointer;\n transition: transform 0.1s;\n flex-shrink: 0;\n }\n\n .color-indicator {\n border: 1px solid #555;\n }\n .color-indicator:hover {\n transform: scale(1.2);\n border-color: #fff;\n }\n\n .boolean-checkbox {\n appearance: none;\n -webkit-appearance: none;\n background: transparent;\n border: 1.5px solid var(--control-border, #c0c0c0);\n border-radius: 2px;\n margin: 0;\n position: relative;\n }\n .boolean-checkbox:checked {\n border-color: var(--control-color, #000080);\n }\n .boolean-checkbox:checked::after {\n content: '✔';\n color: var(--control-color, #000080);\n font-size: 11px;\n font-weight: bold;\n position: absolute;\n top: -3px;\n right: -1px;\n }\n .boolean-checkbox:hover {\n transform: scale(1.2);\n border-color: var(--control-color, #000080);\n }\n\n .collapse-button {\n background: transparent;\n border: none;\n color: var(--json-punct, #a9b7c6);\n font-size: 10px;\n display: flex;\n align-items: center;\n justify-content: center;\n user-select: none;\n opacity: 0;\n transition: opacity 0.15s;\n }\n .collapse-button.collapsed {\n opacity: 1;\n }\n .gutter:hover .collapse-button {\n opacity: 1;\n }\n .collapse-button:hover {\n transform: scale(1.2);\n }\n\n .visibility-button {\n width: 14px;\n height: 14px;\n background: transparent;\n color: var(--control-color, #000080);\n border: none;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.1s;\n flex-shrink: 0;\n opacity: 0.7;\n padding: 0;\n font-size: 11px;\n }\n .visibility-button:hover { opacity: 1; transform: scale(1.15); }\n .visibility-button.hidden { opacity: 0.35; }\n\n .line-hidden { opacity: 0.35; filter: grayscale(50%); }\n\n .editor-content {\n position: relative;\n flex: 1;\n overflow: hidden;\n }\n\n .highlight-layer, textarea, .placeholder-layer {\n position: absolute;\n inset: 0;\n padding: 8px 12px;\n white-space: pre-wrap;\n word-wrap: break-word;\n }\n\n .highlight-layer {\n overflow: auto;\n pointer-events: none;\n z-index: 1;\n color: var(--text-color, #000);\n }\n .highlight-layer::-webkit-scrollbar { display: none; }\n\n textarea {\n margin: 0;\n border: none;\n outline: none;\n background: transparent;\n color: transparent;\n caret-color: var(--caret-color, #000);\n resize: none;\n overflow: auto;\n z-index: 2;\n }\n textarea::selection { background: rgba(51,153,255,0.3); }\n textarea::placeholder { color: transparent; }\n textarea:disabled { cursor: not-allowed; opacity: 0.6; }\n\n .placeholder-layer {\n color: #6a6a6a;\n pointer-events: none;\n z-index: 0;\n overflow: hidden;\n }\n\n .json-key { color: var(--json-key, #660e7a); }\n .json-string { color: var(--json-string, #008000); }\n .json-number { color: var(--json-number, #00f); }\n .json-boolean, .json-null { color: var(--json-boolean, #000080); }\n .json-punctuation { color: var(--json-punct, #000); }\n .json-key-invalid { color: var(--json-key-invalid, #f00); }\n\n .geojson-key { color: var(--geojson-key, #660e7a); font-weight: 600; }\n .geojson-type { color: var(--geojson-type, #008000); font-weight: 600; }\n .geojson-type-invalid { color: var(--geojson-type-invalid, #f00); font-weight: 600; }\n\n .prefix-wrapper, .suffix-wrapper {\n display: flex;\n flex-shrink: 0;\n background: var(--bg-color, #fff);\n }\n\n .prefix-gutter, .suffix-gutter {\n width: 24px;\n background: var(--gutter-bg, #f0f0f0);\n border-right: 1px solid var(--gutter-border, #e0e0e0);\n flex-shrink: 0;\n }\n\n .editor-prefix, .editor-suffix {\n flex: 1;\n padding: 4px 12px;\n color: var(--text-color, #000);\n background: var(--bg-color, #fff);\n user-select: none;\n white-space: pre-wrap;\n word-wrap: break-word;\n opacity: 0.6;\n }\n\n .prefix-wrapper { border-bottom: 1px solid rgba(255,255,255,0.1); }\n .suffix-wrapper { border-top: 1px solid rgba(255,255,255,0.1); position: relative; }\n\n .clear-btn {\n position: absolute;\n right: 0.5rem;\n top: 50%;\n transform: translateY(-50%);\n background: transparent;\n border: none;\n color: var(--text-color, #000);\n opacity: 0.3;\n cursor: pointer;\n font-size: 0.65rem;\n width: 1rem;\n height: 1rem;\n padding: 0.15rem 0 0 0;\n border-radius: 3px;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: opacity 0.2s, background 0.2s;\n }\n .clear-btn:hover { opacity: 0.7; background: rgba(255,255,255,0.1); }\n .clear-btn[hidden] { display: none; }\n\n textarea::-webkit-scrollbar { width: 10px; height: 10px; }\n textarea::-webkit-scrollbar-track { background: var(--control-bg, #e8e8e8); }\n textarea::-webkit-scrollbar-thumb { background: var(--control-border, #c0c0c0); border-radius: 5px; }\n textarea::-webkit-scrollbar-thumb:hover { background: var(--control-color, #000080); }\n textarea { scrollbar-width: thin; scrollbar-color: var(--control-border, #c0c0c0) var(--control-bg, #e8e8e8); }\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",()=>{this.updatePlaceholderVisibility(),clearTimeout(this.highlightTimer),this.highlightTimer=setTimeout(()=>{this.autoFormatContentWithCursor(),this.updateHighlight(),this.emitChange()},150)}),e.addEventListener("paste",()=>{clearTimeout(this.highlightTimer),setTimeout(()=>{this.updatePlaceholderVisibility(),this.autoFormatContentWithCursor(),this.updateHighlight(),this.emitChange(),this.applyAutoCollapsed()},10)}),this.shadowRoot.getElementById("gutterContent").addEventListener("click",e=>{const t=e.target.closest(".visibility-button");if(t){const e=t.dataset.featureKey;return void this.toggleFeatureVisibility(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("boolean-checkbox")){const t=parseInt(e.target.dataset.line),n=e.target.dataset.attributeName,o=e.target.checked;this.updateBooleanValue(t,o,n)}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.shadowRoot.getElementById("clearBtn").addEventListener("click",()=>{this.removeAll()}),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);const t=this.shadowRoot.getElementById("clearBtn");t&&(t.hidden=this.readonly)}escapeHtml(t){if(!t)return"";const n=e.REGEX;return t.replace(n.ampersand,"&amp;").replace(n.lessThan,"&lt;").replace(n.greaterThan,"&gt;")}updatePlaceholderVisibility(){const e=this.shadowRoot.getElementById("textarea"),t=this.shadowRoot.getElementById("placeholderLayer");e&&t&&(t.style.display=e.value?"none":"block")}updatePlaceholderContent(){const e=this.shadowRoot.getElementById("placeholderLayer");e&&(e.textContent=this.placeholder),this.updatePlaceholderVisibility()}updateValue(e){const t=this.shadowRoot.getElementById("textarea");if(t&&t.value!==e){if(t.value=e||"",e)try{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=""}catch(n){}this.updateHighlight(),this.updatePlaceholderVisibility(),t.value&&requestAnimationFrame(()=>{this.applyAutoCollapsed()}),this.emitChange()}}updatePrefixSuffix(){const e=this.shadowRoot.getElementById("editorPrefix"),t=this.shadowRoot.getElementById("editorSuffix");e&&(e.textContent=this.prefix),t&&(t.textContent=this.suffix)}updateHighlight(){const e=this.shadowRoot.getElementById("textarea"),t=this.shadowRoot.getElementById("highlightLayer");if(!e||!t)return;const n=e.value;this.updateFeatureRanges();const o=this.getHiddenLineRanges(),{highlighted:s,colors:r,booleans:a,toggles:i}=this.highlightJSON(n,o);t.innerHTML=s,this.colorPositions=r,this.booleanPositions=a,this.nodeTogglePositions=i,this.updateGutter()}highlightJSON(t,n=[]){if(!t.trim())return{highlighted:"",colors:[],booleans:[],toggles:[]};const o=t.split("\n"),s=[],r=[],a=[];let i=[];const l=this.buildContextMap(t);return o.forEach((t,o)=>{const c=e.REGEX;let d,h;for(c.colorInLine.lastIndex=0;null!==(d=c.colorInLine.exec(t));)s.push({line:o,color:d[2],attributeName:d[1]});for(c.booleanInLine.lastIndex=0;null!==(h=c.booleanInLine.exec(t));)r.push({line:o,value:"true"===h[2],attributeName:h[1]});const u=t.match(c.collapsibleNode);if(u){const e=u[2];t.includes("{...}")||t.includes("[...]")?a.push({line:o,nodeKey:e,isCollapsed:!0}):this.bracketClosesOnSameLine(t,u[3])||a.push({line:o,nodeKey:e,isCollapsed:!1})}const p=l.get(o);let g=this.highlightSyntax(t,p);(e=>n.some(t=>e>=t.startLine&&e<=t.endLine))(o)&&(g=`<span class="line-hidden">${g}</span>`),i.push(g)}),{highlighted:i.join("\n"),colors:s,booleans:r,toggles:a}}static GEOJSON={GEOMETRY_TYPES:["Point","MultiPoint","LineString","MultiLineString","Polygon","MultiPolygon"]};static VALID_KEYS_BY_CONTEXT={Feature:["type","geometry","properties","id"],properties:null,geometry:["type","coordinates"]};static CONTEXT_CHANGING_KEYS={geometry:"geometry",properties:"properties",features:"Feature"};buildContextMap(t){const n=t.split("\n"),o=/* @__PURE__ */new Map,s=[];let r=null;const a="Feature";for(let i=0;i<n.length;i++){const t=n[i],l=s.length>0?s[s.length-1]?.context:a;o.set(i,l);let c=!1,d=!1;for(let n=0;n<t.length;n++){const o=t[n];if(d)d=!1;else if("\\"===o&&c)d=!0;else if('"'!==o){if(!c){if("{"===o||"["===o){let e;if(r)e=r,r=null;else if(0===s.length)e=a;else{const t=s[s.length-1];e=t&&t.isArray?t.context:null}s.push({context:e,isArray:"["===o})}"}"!==o&&"]"!==o||s.length>0&&s.pop()}}else{if(!c){const o=t.substring(n).match(/^"([^"\\]*(?:\\.[^"\\]*)*)"\s*:/);if(o){const t=o[1];e.CONTEXT_CHANGING_KEYS[t]&&(r=e.CONTEXT_CHANGING_KEYS[t]),n+=o[0].length-1;continue}if(s.length>0&&t.substring(0,n).match(/"type"\s*:\s*$/)){const o=t.substring(n).match(/^"([^"\\]*(?:\\.[^"\\]*)*)"/),r=["Feature",...e.GEOJSON.GEOMETRY_TYPES];if(o&&r.includes(o[1])){const e=s[s.length-1];e&&(e.context=o[1])}n+=o?o[0].length-1:0;continue}}c=!c}}}return o}static GEOJSON_STRUCTURAL_KEYS=["type","geometry","properties","coordinates","id"];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,"&amp;").replace(s.lessThan,"&lt;").replace(s.greaterThan,"&gt;").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.GEOMETRY_TYPES.includes(n)?e.GEOJSON.GEOMETRY_TYPES.includes(t):"Feature"!==n||"Feature"===t||e.GEOJSON.GEOMETRY_TYPES.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("[...]")){const n=s.match(/^(\s*)/)[1].length,r=this._findCollapsedData(t,e,n);if(!r)return;const{key:a,data:i}=r,{originalLine:l,content:c}=i;o[t]=l,o.splice(t+1,0,...c),this.collapsedData.delete(a)}else{const n=s.match(/^(\s*)"([^"]+)"\s*:\s*([{\[])/);if(!n)return;const r=n[1],a=n[3];if(0===this._performCollapse(o,t,e,r,a))return}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 n=t.length-1;n>=0;n--){const e=t[n].match(/^(\s*)"(\w+)"\s*:\s*([{\[])/);if(e){const o=e[2];if("coordinates"===o){const s=e[1],r=e[3];this._performCollapse(t,n,o,s,r)}}}e.value=t.join("\n"),this.updateHighlight()}updateGutter(){const t=this.shadowRoot.getElementById("gutterContent"),n=this.shadowRoot.getElementById("textarea");if(!n)return;if(null===this._cachedLineHeight){const e=getComputedStyle(n);this._cachedLineHeight=parseFloat(e.lineHeight),this._cachedPaddingTop=parseFloat(e.paddingTop)}const o=this._cachedLineHeight,s=this._cachedPaddingTop;t.textContent="";const r=/* @__PURE__ */new Map,a=e=>(r.has(e)||r.set(e,{colors:[],booleans:[],buttons:[],visibilityButtons:[]}),r.get(e));this.colorPositions.forEach(({line:e,color:t,attributeName:n})=>{a(e).colors.push({color:t,attributeName:n})}),this.booleanPositions.forEach(({line:e,value:t,attributeName:n})=>{a(e).booleans.push({value:t,attributeName:n})}),this.nodeTogglePositions.forEach(({line:e,nodeKey:t,isCollapsed:n})=>{a(e).buttons.push({nodeKey:t,isCollapsed:n})});for(const[e,l]of this.featureRanges){const t=this.hiddenFeatures.has(e);a(l.startLine).visibilityButtons.push({featureKey:e,isHidden:t})}const i=document.createDocumentFragment();r.forEach((t,n)=>{const r=document.createElement("div");r.className="gutter-line",r.style.top=`${s+n*o}px`,t.visibilityButtons.forEach(({featureKey:t,isHidden:n})=>{const o=document.createElement("button");o.className="visibility-button"+(n?" hidden":""),o.textContent=e.ICONS.visibility,o.dataset.featureKey=t,o.title=n?"Show feature in events":"Hide feature from events",r.appendChild(o)}),t.colors.forEach(({color:e,attributeName:t})=>{const o=document.createElement("div");o.className="color-indicator",o.style.backgroundColor=e,o.dataset.line=n,o.dataset.color=e,o.dataset.attributeName=t,o.title=`${t}: ${e}`,r.appendChild(o)}),t.booleans.forEach(({value:e,attributeName:t})=>{const o=document.createElement("input");o.type="checkbox",o.className="boolean-checkbox",o.checked=e,o.dataset.line=n,o.dataset.attributeName=t,o.title=`${t}: ${e}`,r.appendChild(o)}),t.buttons.forEach(({nodeKey:t,isCollapsed:o})=>{const s=document.createElement("div");s.className=o?"collapse-button collapsed":"collapse-button",s.textContent=o?e.ICONS.collapsed:e.ICONS.expanded,s.dataset.line=n,s.dataset.nodeKey=t,s.title=o?"Expand":"Collapse",r.appendChild(s)}),i.appendChild(r)}),t.appendChild(i)}showColorPicker(e,t,n,o){const s=document.querySelector(".geojson-color-picker-input");s&&(s._closeListener&&document.removeEventListener("click",s._closeListener,!0),s.remove());const r=document.createElement("input");r.type="color",r.value=n,r.className="geojson-color-picker-input";const a=e.getBoundingClientRect();r.style.position="fixed",r.style.left=`${a.left}px`,r.style.top=`${a.top}px`,r.style.width="12px",r.style.height="12px",r.style.opacity="0.01",r.style.border="none",r.style.padding="0",r.style.zIndex="9999",r.addEventListener("input",e=>{this.updateColorValue(t,e.target.value,o)}),r.addEventListener("change",e=>{this.updateColorValue(t,e.target.value,o)});const i=e=>{e.target===r||r.contains(e.target)||(document.removeEventListener("click",i,!0),r.remove())};r._closeListener=i,document.body.appendChild(r),setTimeout(()=>{document.addEventListener("click",i,!0)},100),r.focus(),r.click()}updateColorValue(e,t,n){const o=this.shadowRoot.getElementById("textarea"),s=o.value.split("\n"),r=new RegExp(`"${n}"\\s*:\\s*"#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})"`);s[e]=s[e].replace(r,`"${n}": "${t}"`),o.value=s.join("\n"),this.updateHighlight(),this.emitChange()}updateBooleanValue(e,t,n){const o=this.shadowRoot.getElementById("textarea"),s=o.value.split("\n"),r=new RegExp(`"${n}"\\s*:\\s*(true|false)`);s[e]=s[e].replace(r,`"${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))return;if(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;let r;r=0===n&&o===t.value.length?this.expandAllCollapsed(s):this.expandCollapsedMarkersInText(s,n),e.preventDefault(),e.clipboardData.setData("text/plain",r)}expandCollapsedMarkersInText(t,n){const o=this.shadowRoot.getElementById("textarea").value.substring(0,n).split("\n").length-1,s=e.REGEX,r=t.split("\n"),a=[];return r.forEach((e,t)=>{const n=o+t;if(e.includes("{...}")||e.includes("[...]")){const t=e.match(s.collapsedMarker);if(t){const e=t[2],o=t[1].length,s=this._findCollapsedData(n,e,o);if(s)return a.push(s.data.originalLine),void a.push(...s.data.content);for(const[,t]of this.collapsedData.entries())if(t.nodeKey===e)return a.push(t.originalLine),void a.push(...t.content)}a.push(e)}else a.push(e)}),a.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.updatePlaceholderVisibility(),this.emitChange()}}emitChange(){const e=this.shadowRoot.getElementById("textarea"),t=this.expandAllCollapsed(e.value),n=this.prefix+t+this.suffix;try{let e=JSON.parse(n);e=this.filterHiddenFeatures(e);let o=[];e.features.forEach((e,t)=>{o.push(...this.validateGeoJSON(e,`features[${t}]`,"root"))}),o.length>0?this.dispatchEvent(new CustomEvent("error",{detail:{timestamp:/* @__PURE__ */(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(o){this.dispatchEvent(new CustomEvent("error",{detail:{timestamp:/* @__PURE__ */(new Date).toISOString(),error:o.message,content:t},bubbles:!0,composed:!0}))}}filterHiddenFeatures(e){if(!e||0===this.hiddenFeatures.size)return e;const t=e.features.filter(e=>{const t=this.getFeatureKey(e);return!this.hiddenFeatures.has(t)});return{...e,features:t}}getFeatureKey(e){if(!e||"object"!=typeof e)return null;if(void 0!==e.id)return`id:${e.id}`;if(void 0!==e.properties?.id)return`prop:${e.properties.id}`;const t=e.geometry?.type||"null",n=JSON.stringify(e.geometry?.coordinates||[]);return`hash:${t}:${this.simpleHash(n)}`}simpleHash(e){let t=0;for(let n=0;n<e.length;n++)t=(t<<5)-t+e.charCodeAt(n),t&=t;return t.toString(36)}toggleFeatureVisibility(e){this.hiddenFeatures.has(e)?this.hiddenFeatures.delete(e):this.hiddenFeatures.add(e),this.updateHighlight(),this.updateGutter(),this.emitChange()}updateFeatureRanges(){const e=this.shadowRoot.getElementById("textarea");if(!e)return;const t=e.value;this.featureRanges.clear();try{const e=this.expandAllCollapsed(t),n=this.prefix+e+this.suffix,o=JSON.parse(n).features,s=t.split("\n");let r=0,a=0,i=!1,l=-1,c=null;for(let t=0;t<s.length;t++){const e=s[t],n=/"type"\s*:\s*"Feature"/.test(e);if(!i&&n){let e=t;for(let n=t;n>=0;n--){const t=s[n].trim();if("{"===t||"{,"===t){e=n;break}if(t.startsWith("{")&&!t.includes(":")){e=n;break}}l=e,i=!0,a=1;for(let n=e;n<=t;n++){const t=s[n],o=this._countBracketsOutsideStrings(t,"{");a+=n===e?o.open-1-o.close:o.open-o.close}r<o.length&&(c=this.getFeatureKey(o[r]))}else if(i){const n=this._countBracketsOutsideStrings(e,"{");a+=n.open-n.close,a<=0&&(c&&this.featureRanges.set(c,{startLine:l,endLine:t,featureIndex:r}),r++,i=!1,c=null)}}}catch(n){}}getHiddenLineRanges(){const e=[];for(const[t,n]of this.featureRanges)this.hiddenFeatures.has(t)&&e.push(n);return e}validateGeoJSON(t,n="",o="root"){const s=[];if(!t||"object"!=typeof t)return s;if("properties"!==o&&void 0!==t.type){const r=t.type;"string"==typeof r&&("geometry"===o?e.GEOJSON.GEOMETRY_TYPES.includes(r)||s.push(`Invalid geometry type "${r}" at ${n||"root"} (expected: ${e.GEOJSON.GEOMETRY_TYPES.join(", ")})`):"Feature"!==r&&s.push(`Invalid type "${r}" at ${n||"root"} (expected: Feature)`))}if(Array.isArray(t))t.forEach((e,t)=>{s.push(...this.validateGeoJSON(e,`${n}[${t}]`,o))});else for(const[e,r]of Object.entries(t))if("object"==typeof r&&null!==r){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(r,t,a))}return s}_countBracketsOutsideStrings(e,t){const n="{"===t?"}":"]";let o=0,s=0,r=!1,a=!1;for(let i=0;i<e.length;i++){const l=e[i];a?a=!1:"\\"===l&&r?a=!0:'"'!==l?r||(l===t&&o++,l===n&&s++):r=!r}return{open:o,close:s}}bracketClosesOnSameLine(e,t){const n=e.indexOf(t);if(-1===n)return!1;const o=e.substring(n+1),s=this._countBracketsOutsideStrings(o,t);return s.close>s.open}_findClosingBracket(e,t,n){let o=1;const s=[],r=e[t],a=r.indexOf(n);if(-1!==a){const e=r.substring(a+1),s=this._countBracketsOutsideStrings(e,n);if(o+=s.open-s.close,0===o)return{endLine:t,content:[]}}for(let i=t+1;i<e.length;i++){const t=e[i],r=this._countBracketsOutsideStrings(t,n);if(o+=r.open-r.close,s.push(t),0===o)return{endLine:i,content:s}}return null}_performCollapse(e,t,n,o,s){const r=e[t],a="{"===s?"}":"]";if(this.bracketClosesOnSameLine(r,s))return 0;const i=this._findClosingBracket(e,t,s);if(!i)return 0;const{endLine:l,content:c}=i,d=`${t}-${n}`;this.collapsedData.set(d,{originalLine:r,content:c,indent:o.length,nodeKey:n});const h=r.substring(0,r.indexOf(s)),u=e[l]&&e[l].trim().endsWith(",");e[t]=`${h}${s}...${a}${u?",":""}`;const p=l-t;return e.splice(t+1,p),p}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 r=s.match(n.collapsedMarker);if(!r)continue;const a=r[2],i=r[1].length,l=this._findCollapsedData(t,a,i);if(l){const{data:{originalLine:n,content:s}}=l;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="["+e+"]";let n=JSON.parse(t);Array.isArray(n)&&(n=n.map(e=>this._applyDefaultPropertiesToFeature(e)));const o=JSON.stringify(n,null,2).split("\n");return o.length>2?o.slice(1,-1).join("\n"):""}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,r=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,r.length>0&&this.reapplyCollapsed(r);const n=e.value.split("\n");if(o<n.length){const t=Math.min(s,n[o].length);let r=0;for(let e=0;e<o;e++)r+=n[e].length+1;r+=t,e.setSelectionRange(r,r)}}}catch(i){}}reapplyCollapsed(e){const t=this.shadowRoot.getElementById("textarea"),n=t.value.split("\n"),o=/* @__PURE__ */new Map;e.forEach(({nodeKey:e,indent:t})=>{const n=`${e}-${t}`;o.set(n,(o.get(n)||0)+1)});const s=/* @__PURE__ */new Map;for(let r=n.length-1;r>=0;r--){const e=n[r].match(/^(\s*)"(\w+)"\s*:\s*([{\[])/);if(e){const t=e[2],a=`${t}-${e[1].length}`;if(o.has(a)&&(s.set(a,(s.get(a)||0)+1),s.get(a)<=o.get(a))){const o=e[1],s=e[3];this._performCollapse(n,r,t,o,s)}}}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 t=this.getAttribute("dark-selector")||".dark",n=this.parseSelectorToHostRule(t);let o=this.shadowRoot.getElementById("theme-styles");o||(o=document.createElement("style"),o.id="theme-styles",this.shadowRoot.insertBefore(o,this.shadowRoot.firstChild));const s=t=>Object.entries(t||{}).map(([t,n])=>`--${e._toKebabCase(t)}: ${n};`).join("\n "),r=s(this.themes.light);let a="";r&&(a+=`:host {\n ${r}\n }\n`),a+=`${n} {\n ${s({...e.DARK_THEME_DEFAULTS,...this.themes.dark})}\n }`,o.textContent=a}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:{},light:{}},this.updateThemeCSS()}_normalizeIndex(e,t,n=!1){let o=e;return o<0&&(o=t+o),n?Math.max(0,Math.min(o,t)):o<0||o>=t?-1:o}_parseFeatures(){const e=this.shadowRoot.getElementById("textarea");if(!e||!e.value.trim())return[];try{const t="["+this.expandAllCollapsed(e.value)+"]";return JSON.parse(t)}catch(t){return[]}}_setFeatures(e){const t=this.shadowRoot.getElementById("textarea");if(t){if(this.collapsedData.clear(),this.hiddenFeatures.clear(),e&&0!==e.length){const n=e.map(e=>JSON.stringify(e,null,2)).join(",\n");t.value=n}else t.value="";this.updateHighlight(),this.updatePlaceholderVisibility(),t.value&&requestAnimationFrame(()=>{this.applyAutoCollapsed()}),this.emitChange()}}_validateFeature(t){const n=[];return t&&"object"==typeof t?Array.isArray(t)?(n.push("Feature cannot be an array"),n):("type"in t?"Feature"!==t.type&&n.push(`Feature type must be "Feature", got "${t.type}"`):n.push('Feature must have a "type" property'),"geometry"in t?null!==t.geometry&&("object"!=typeof t.geometry||Array.isArray(t.geometry)?n.push("Feature geometry must be an object or null"):("type"in t.geometry?e.GEOJSON.GEOMETRY_TYPES.includes(t.geometry.type)||n.push(`Invalid geometry type "${t.geometry.type}" (expected: ${e.GEOJSON.GEOMETRY_TYPES.join(", ")})`):n.push('Geometry must have a "type" property'),"coordinates"in t.geometry||n.push('Geometry must have a "coordinates" property'))):n.push('Feature must have a "geometry" property (can be null)'),"properties"in t?null===t.properties||"object"==typeof t.properties&&!Array.isArray(t.properties)||n.push("Feature properties must be an object or null"):n.push('Feature must have a "properties" property (can be null)'),n):(n.push("Feature must be an object"),n)}set(e){if(!Array.isArray(e))throw new Error("set() expects an array of features");const t=[];if(e.forEach((e,n)=>{const o=this._validateFeature(e);o.length>0&&t.push(`Feature[${n}]: ${o.join(", ")}`)}),t.length>0)throw new Error(`Invalid features: ${t.join("; ")}`);const n=e.map(e=>this._applyDefaultPropertiesToFeature(e));this._setFeatures(n)}add(e){const t=this._validateFeature(e);if(t.length>0)throw new Error(`Invalid feature: ${t.join(", ")}`);const n=this._parseFeatures();n.push(this._applyDefaultPropertiesToFeature(e)),this._setFeatures(n)}insertAt(e,t){const n=this._validateFeature(e);if(n.length>0)throw new Error(`Invalid feature: ${n.join(", ")}`);const o=this._parseFeatures(),s=this._normalizeIndex(t,o.length,!0);o.splice(s,0,this._applyDefaultPropertiesToFeature(e)),this._setFeatures(o)}removeAt(e){const t=this._parseFeatures();if(0===t.length)return;const n=this._normalizeIndex(e,t.length);if(-1===n)return;const o=t.splice(n,1)[0];return this._setFeatures(t),o}removeAll(){const e=this._parseFeatures();return this._setFeatures([]),e}get(e){const t=this._parseFeatures();if(0===t.length)return;const n=this._normalizeIndex(e,t.length);return-1!==n?t[n]:void 0}getAll(){return this._parseFeatures()}emit(){this.emitChange()}}customElements.define("geojson-editor",e);export{e as default};
9
+ const e=["type","geometry","properties","coordinates","id","features"],t=["Point","MultiPoint","LineString","MultiLineString","Polygon","MultiPolygon"];class s extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"}),this.lines=[],this.collapsedNodes=/* @__PURE__ */new Set,this.hiddenFeatures=/* @__PURE__ */new Set,this._nodeIdCounter=0,this._lineToNodeId=/* @__PURE__ */new Map,this._nodeIdToLines=/* @__PURE__ */new Map,this.visibleLines=[],this.lineMetadata=/* @__PURE__ */new Map,this.featureRanges=/* @__PURE__ */new Map,this.scrollTop=0,this.viewportHeight=0,this.lineHeight=19.5,this.bufferLines=5,this._lastStartIndex=-1,this._lastEndIndex=-1,this._lastTotalLines=-1,this._scrollRaf=null,this.cursorLine=0,this.cursorColumn=0,this.selectionStart=null,this.selectionEnd=null,this.renderTimer=null,this.inputTimer=null,this.themes={dark:{},light:{}}}_generateNodeId(){return"node_"+ ++this._nodeIdCounter}_getCollapsedRangeForLine(e){for(const[t,s]of this._nodeIdToLines)if(this.collapsedNodes.has(t)&&e>s.startLine&&e<s.endLine)return{nodeId:t,...s};return null}_getCollapsedClosingLine(e){for(const[t,s]of this._nodeIdToLines)if(this.collapsedNodes.has(t)&&e===s.endLine)return{nodeId:t,...s};return null}_getClosingBracketPos(e){return Math.max(e.lastIndexOf("]"),e.lastIndexOf("}"))}_getCollapsedNodeAtLine(e){const t=this._lineToNodeId.get(e);return t&&this.collapsedNodes.has(t)?{nodeId:t,...this._nodeIdToLines.get(t)}:null}_getCollapsibleNodeAtLine(e){const t=this._lineToNodeId.get(e);if(t){const e=this._nodeIdToLines.get(t);return{nodeId:t,isCollapsed:this.collapsedNodes.has(t),...e}}return null}_getContainingExpandedNode(e){let t=null;for(const[s,i]of this._nodeIdToLines)this.collapsedNodes.has(s)||e>=i.startLine&&e<=i.endLine&&(!t||i.endLine-i.startLine<t.endLine-t.startLine)&&(t={nodeId:s,...i});return t}_deleteCollapsedNode(e){const t=e.endLine-e.startLine+1;this.lines.splice(e.startLine,t),this.cursorLine=Math.min(e.startLine,this.lines.length-1),this.cursorColumn=0,this.formatAndUpdate()}_rebuildNodeIdMappings(){const e=new Set(this.collapsedNodes),t=/* @__PURE__ */new Map;for(const[n,o]of this._nodeIdToLines)o.nodeKey&&t.set(n,o.nodeKey);const s=[];for(const n of e){const e=t.get(n);e&&s.push(e)}this._nodeIdCounter=0,this._lineToNodeId.clear(),this._nodeIdToLines.clear(),this.collapsedNodes.clear();const i=/* @__PURE__ */new Map;for(let n=0;n<this.lines.length;n++){const e=this.lines[n],t=e.match(/^\s*"([^"]+)"\s*:\s*([{\[])/),o=!t&&e.match(/^\s*([{\[]),?\s*$/);if(!t&&!o)continue;let r,l;t?(r=t[1],l=t[2]):(l=o[1],r=`__root_${l}_${n}`);const a=e.substring(e.indexOf(l)+1),c=this._countBrackets(a,l);if(c.close>c.open)continue;const h=this._findClosingLine(n,l);if(-1===h||h===n)continue;const d=this._generateNodeId();this._lineToNodeId.set(n,d),this._nodeIdToLines.set(d,{startLine:n,endLine:h,nodeKey:r,isRootFeature:!!o});const u=i.get(r)||0;i.set(r,u+1);const p=s.indexOf(r);-1!==p&&(s.splice(p,1),this.collapsedNodes.add(d))}}static get observedAttributes(){return["readonly","value","placeholder","dark-selector"]}connectedCallback(){this.render(),this.setupEventListeners(),this.updatePrefixSuffix(),this.updateThemeCSS(),this.value&&this.setValue(this.value),this.updatePlaceholderVisibility()}disconnectedCallback(){this.renderTimer&&clearTimeout(this.renderTimer),this.inputTimer&&clearTimeout(this.inputTimer);const e=document.querySelector(".geojson-color-picker-input");e&&(e._closeListener&&document.removeEventListener("click",e._closeListener,!0),e.remove())}attributeChangedCallback(e,t,s){if(t!==s)switch(e){case"value":this.setValue(s);break;case"readonly":this.updateReadonly();break;case"placeholder":this.updatePlaceholderContent();break;case"dark-selector":this.updateThemeCSS()}}get readonly(){return this.hasAttribute("readonly")}get value(){return this.getAttribute("value")||""}get placeholder(){return this.getAttribute("placeholder")||""}get prefix(){return'{"type": "FeatureCollection", "features": ['}get suffix(){return"]}"}render(){const e=document.createElement("style");e.textContent=':host *,:host *:before,:host *:after{box-sizing:border-box;font: 13px/1.5 Courier New,Courier,monospace;font-variant:normal;letter-spacing:0;word-spacing:0;text-transform:none;text-decoration:none;text-indent:0}:host{display:flex;flex-direction:column;position:relative;width:100%;height:400px;border-radius:4px;overflow:hidden}:host([readonly]) .editor-wrapper:after{content:"";position:absolute;inset:0;pointer-events:none;background:repeating-linear-gradient(-45deg,rgba(128,128,128,.08),rgba(128,128,128,.08) 3px,transparent 3px,transparent 12px);z-index:1}:host([readonly]) .hidden-textarea{cursor:text}.editor-wrapper{position:relative;width:100%;flex:1;background:var(--bg-color, #fff);display:flex;overflow:hidden}.gutter{width:50px;background:var(--gutter-bg, #f0f0f0);border-right:1px solid var(--gutter-border, #e0e0e0);overflow:hidden;flex-shrink:0;position:relative}.gutter-scroll{position:absolute;inset:0;overflow:hidden;padding:8px 0}.gutter-scroll-content{position:relative;width:100%}.gutter-content{position:absolute;top:0;left:0;right:0;will-change:transform}.gutter-line{height:19.5px;display:flex;align-items:center;justify-content:flex-end;padding-right:4px}.line-number{font-size:11px;color:var(--gutter-text, #999);-webkit-user-select:none;user-select:none;min-width:20px;text-align:right}.collapse-column{width:16px;display:flex;align-items:center;justify-content:center;flex-shrink:0}.color-indicator,.collapse-button,.boolean-checkbox{width:12px;height:12px;border-radius:2px;cursor:pointer;transition:transform .1s;flex-shrink:0}.color-indicator{border:1px solid #555}.color-indicator:hover{transform:scale(1.2);border-color:#fff}.boolean-checkbox{appearance:none;-webkit-appearance:none;background:transparent;border:1.5px solid var(--control-border, #c0c0c0);border-radius:2px;margin:0;position:relative}.boolean-checkbox:checked{border-color:var(--control-color, #000080)}.boolean-checkbox:checked:after{content:"✔";color:var(--control-color, #000080);font-size:11px;font-weight:700;position:absolute;top:-3px;right:-1px}.boolean-checkbox:hover{transform:scale(1.2);border-color:var(--control-color, #000080)}.collapse-button{background:transparent;border:none;color:var(--json-punct, #a9b7c6);font-size:10px;display:flex;align-items:center;justify-content:center;-webkit-user-select:none;user-select:none;opacity:0;transition:opacity .15s}.collapse-button.collapsed,.gutter:hover .collapse-button{opacity:1}.collapse-button:hover{transform:scale(1.2)}@media(hover:none),(pointer:coarse){.collapse-button{opacity:1}}.visibility-button{width:14px;height:14px;background:transparent;color:var(--control-color, #000080);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .1s;flex-shrink:0;opacity:.7;padding:0;font-size:11px}.visibility-button:hover{opacity:1;transform:scale(1.15)}.visibility-button.hidden{opacity:.35}.editor-content{position:relative;flex:1;overflow:hidden}.hidden-textarea{position:absolute;top:0;left:0;width:100%;height:100%;opacity:0;padding:0;margin:0;border:none;outline:none;resize:none;overflow:hidden;z-index:-1;pointer-events:none;caret-color:transparent}.viewport{position:absolute;inset:0;overflow:auto;padding:8px 12px;overscroll-behavior:contain}.scroll-content{position:relative;width:100%}.lines-container{position:absolute;top:0;left:0;right:0;will-change:transform}.line{height:19.5px;white-space:pre;display:block;position:relative}.cursor{position:absolute;width:2px;height:1em;background:var(--caret-color, #000);top:.15em;pointer-events:none;animation:cursor-blink 1s step-end infinite}@keyframes cursor-blink{0%,to{opacity:1}50%{opacity:0}}.selection{position:absolute;height:100%;top:0;background:var(--selection-color, rgba(51, 153, 255, .3));pointer-events:none;z-index:0}.line-hidden>span{opacity:.35;filter:grayscale(50%)}.line-collapsed{display:none}.placeholder-layer{position:absolute;top:8px;left:12px;color:#6a6a6a;pointer-events:none;z-index:0;white-space:pre}.json-key{color:var(--json-key, #660e7a)}.json-string{color:var(--json-string, #008000)}.json-number{color:var(--json-number, #00f)}.json-boolean,.json-null{color:var(--json-boolean, #000080)}.json-punctuation{color:var(--json-punct, #000)}.json-key-invalid{color:var(--json-key-invalid, #f00)}.json-error{color:var(--json-error, #f00)}.geojson-key{color:var(--geojson-key, #660e7a);font-weight:600}.geojson-type{color:var(--geojson-type, #008000);font-weight:600}.geojson-type-invalid{color:var(--geojson-type-invalid, #f00);font-weight:600}.d-none{display:none}.collapsed-bracket-array,.collapsed-bracket-object{color:var(--json-punct, #000)}.collapsed-bracket-array:after,.collapsed-bracket-object:after{content:"…";color:var(--json-punct, #888)}.collapsed-content{display:none}.prefix-wrapper,.suffix-wrapper{display:flex;flex-shrink:0;background:var(--bg-color, #fff)}.prefix-gutter,.suffix-gutter{width:50px;background:var(--gutter-bg, #f0f0f0);border-right:1px solid var(--gutter-border, #e0e0e0);flex-shrink:0}.editor-prefix,.editor-suffix{flex:1;padding:4px 12px;color:var(--text-color, #000);background:var(--bg-color, #fff);-webkit-user-select:none;user-select:none;white-space:pre-wrap;word-wrap:break-word;opacity:.6}.prefix-wrapper{border-bottom:1px solid rgba(255,255,255,.1)}.suffix-wrapper{border-top:1px solid rgba(255,255,255,.1);position:relative}.clear-btn{position:absolute;right:.5rem;top:50%;transform:translateY(-50%);background:transparent;border:none;color:var(--text-color, #000);opacity:.3;cursor:pointer;font-size:.65rem;width:1rem;height:1rem;padding:.15rem 0 0;border-radius:3px;display:flex;align-items:center;justify-content:center;transition:opacity .2s,background .2s}.clear-btn:hover{opacity:.7;background:#ffffff1a}.clear-btn[hidden]{display:none}.viewport::-webkit-scrollbar{width:10px;height:10px}.viewport::-webkit-scrollbar-track{background:var(--control-bg, #e8e8e8)}.viewport::-webkit-scrollbar-thumb{background:var(--control-border, #c0c0c0);border-radius:5px}.viewport::-webkit-scrollbar-thumb:hover{background:var(--control-color, #000080)}.viewport{scrollbar-width:thin;scrollbar-color:var(--control-border, #c0c0c0) var(--control-bg, #e8e8e8)}.line .selected{background:#3399ff4d}.json-color,.json-boolean{position:relative}.json-color:before,.json-boolean:before{content:"";position:absolute;left:-8px;top:50%;transform:translateY(-50%);margin-top:-1px;width:8px;height:8px;border-radius:2px;cursor:pointer}.json-color:hover:before,.json-boolean:hover:before{transform:translateY(-50%) scale(1.2);border-color:var(--control-color, #000080)}.json-color:before{background-color:var(--swatch-color);border:1px solid var(--json-punct, #a9b7c6)}.json-boolean:before{border:1.5px solid var(--control-border, #c0c0c0);background:transparent}.json-bool-true:before{content:"✔";border-color:var(--control-color, #000080);color:var(--control-color, #000080);font-size:8px;display:flex;align-items:center;justify-content:center;line-height:8px}.line.has-visibility{position:relative}.line.has-visibility:before{content:"👁";position:absolute;left:0;top:3px;font-size:10px;color:var(--control-color, #000080);opacity:.6;cursor:pointer;z-index:1}.line.has-visibility:hover:before{opacity:1;transform:scale(1.15)}.line.has-visibility.feature-hidden:before{opacity:.5}';const t=document.createElement("div");for(t.innerHTML=function(e=""){return`\n <div class="prefix-wrapper">\n <div class="prefix-gutter"></div>\n <div class="editor-prefix" id="editorPrefix"></div>\n </div>\n <div class="editor-wrapper">\n <div class="gutter">\n <div class="gutter-scroll" id="gutterScroll">\n <div class="gutter-scroll-content" id="gutterScrollContent">\n <div class="gutter-content" id="gutterContent"></div>\n </div>\n </div>\n </div>\n <div class="editor-content">\n <div class="placeholder-layer" id="placeholderLayer">${t=e,t?t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;"):""}</div>\n <textarea\n class="hidden-textarea"\n id="hiddenTextarea"\n spellcheck="false"\n autocomplete="off"\n autocorrect="off"\n autocapitalize="off"\n tabindex="0"\n ></textarea>\n <div class="viewport" id="viewport">\n <div class="scroll-content" id="scrollContent">\n <div class="lines-container" id="linesContainer"></div>\n </div>\n </div>\n </div>\n </div>\n <div class="suffix-wrapper">\n <div class="suffix-gutter"></div>\n <div class="editor-suffix" id="editorSuffix"></div>\n <button class="clear-btn" id="clearBtn" title="Clear editor">✕</button>\n </div>\n `;var t}(this.placeholder),this.shadowRoot.innerHTML="",this.shadowRoot.appendChild(e);t.firstChild;)this.shadowRoot.appendChild(t.firstChild)}setupEventListeners(){const e=this.shadowRoot.getElementById("hiddenTextarea"),t=this.shadowRoot.getElementById("viewport"),s=this.shadowRoot.getElementById("gutterContent"),i=this.shadowRoot.querySelector(".gutter"),n=this.shadowRoot.getElementById("clearBtn"),o=this.shadowRoot.querySelector(".editor-wrapper");this._isSelecting=!1,t.addEventListener("click",e=>{this.handleEditorClick(e)},!0),t.addEventListener("mousedown",t=>{const s=t.target.closest(".line.has-visibility");if(s){const e=s.getBoundingClientRect();if(t.clientX-e.left<14)return}if(t.target.classList.contains("json-color")||t.target.classList.contains("json-boolean")){const e=t.target.getBoundingClientRect(),s=t.clientX-e.left;if(s<0&&s>=-8)return}t.preventDefault();const i=this._getPositionFromClick(t);t.shiftKey&&this.selectionStart?(this.selectionEnd=i,this.cursorLine=i.line,this.cursorColumn=i.column):(this.cursorLine=i.line,this.cursorColumn=i.column,this.selectionStart={line:i.line,column:i.column},this.selectionEnd=null,this._isSelecting=!0),e.focus(),this._lastStartIndex=-1,this.scheduleRender()}),t.addEventListener("mousemove",e=>{if(!this._isSelecting)return;const s=this._getPositionFromClick(e);this.selectionEnd=s,this.cursorLine=s.line,this.cursorColumn=s.column;const i=t.getBoundingClientRect();e.clientY<i.top+30?t.scrollTop-=20:e.clientY>i.bottom-30&&(t.scrollTop+=20),this._lastStartIndex=-1,this.scheduleRender()}),document.addEventListener("mouseup",()=>{this._isSelecting=!1}),e.addEventListener("focus",()=>{o.classList.add("focused"),this._lastStartIndex=-1,this.scheduleRender()}),e.addEventListener("blur",()=>{o.classList.remove("focused"),this._lastStartIndex=-1,this.scheduleRender()});let r=!1;t.addEventListener("scroll",()=>{r||(this.scrollTop=t.scrollTop,this.syncGutterScroll(),this._scrollRaf||(this._scrollRaf=requestAnimationFrame(()=>{this._scrollRaf=null,r=!0,this.renderViewport(),r=!1})))}),e.addEventListener("input",()=>{this.handleInput()}),e.addEventListener("keydown",e=>{this.handleKeydown(e)}),e.addEventListener("paste",e=>{this.handlePaste(e)}),e.addEventListener("copy",e=>{this.handleCopy(e)}),e.addEventListener("cut",e=>{this.handleCut(e)}),s.addEventListener("click",e=>{this.handleGutterClick(e)}),i.addEventListener("mousedown",e=>{e.preventDefault()}),i.addEventListener("wheel",e=>{e.preventDefault(),t.scrollTop+=e.deltaY}),n.addEventListener("click",()=>{this.removeAll()}),this.updateReadonly()}setValue(e){if(e&&e.trim())try{const t="["+e+"]",s=JSON.parse(t),i=JSON.stringify(s,null,2).split("\n");this.lines=i.slice(1,-1)}catch(t){this.lines=e.split("\n")}else this.lines=[];this.collapsedNodes.clear(),this.hiddenFeatures.clear(),this._lineToNodeId.clear(),this._nodeIdToLines.clear(),this.cursorLine=0,this.cursorColumn=0,this.updateModel(),this.scheduleRender(),this.updatePlaceholderVisibility(),this.lines.length>0&&requestAnimationFrame(()=>{this.autoCollapseCoordinates()}),this.emitChange()}getContent(){return this.lines.join("\n")}updateModel(){this._rebuildNodeIdMappings(),this.computeFeatureRanges(),this.computeLineMetadata(),this.computeVisibleLines()}updateView(){this.computeLineMetadata(),this.computeVisibleLines()}computeFeatureRanges(){this.featureRanges.clear();try{const e=this.lines.join("\n"),t=this.prefix+e+this.suffix,s=JSON.parse(t);if(!s.features)return;let i=0,n=0,o=!1,r=-1,l=null;for(let a=0;a<this.lines.length;a++){const e=this.lines[a];if(!o&&/"type"\s*:\s*"Feature"/.test(e)){let e=a;for(let t=a;t>=0;t--){const s=this.lines[t].trim();if("{"===s||"{,"===s){e=t;break}}r=e,o=!0,n=1;for(let t=e;t<=a;t++){const s=this._countBrackets(this.lines[t],"{");n+=t===e?s.open-1-s.close:s.open-s.close}i<s.features.length&&(l=this._getFeatureKey(s.features[i]))}else if(o){const t=this._countBrackets(e,"{");n+=t.open-t.close,n<=0&&(l&&this.featureRanges.set(l,{startLine:r,endLine:a,featureIndex:i}),i++,o=!1,l=null)}}}catch(e){}}computeLineMetadata(){this.lineMetadata.clear();const e=this._findCollapsibleRanges();for(let t=0;t<this.lines.length;t++){const s=this.lines[t],i={colors:[],booleans:[],collapseButton:null,visibilityButton:null,isHidden:!1,isCollapsed:!1,featureKey:null},n=/"([\w-]+)"\s*:\s*"(#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}))"/g;let o;for(;null!==(o=n.exec(s));)i.colors.push({attributeName:o[1],color:o[2]});const r=/"([\w-]+)"\s*:\s*(true|false)/g;let l;for(;null!==(l=r.exec(s));)i.booleans.push({attributeName:l[1],value:"true"===l[2]});const a=e.find(e=>e.startLine===t);a&&(i.collapseButton={nodeKey:a.nodeKey,nodeId:a.nodeId,isCollapsed:this.collapsedNodes.has(a.nodeId)}),e.find(e=>this.collapsedNodes.has(e.nodeId)&&t>e.startLine&&t<e.endLine)&&(i.isCollapsed=!0);for(const[e,c]of this.featureRanges)if(t>=c.startLine&&t<=c.endLine){i.featureKey=e,this.hiddenFeatures.has(e)&&(i.isHidden=!0),t===c.startLine&&(i.visibilityButton={featureKey:e,isHidden:this.hiddenFeatures.has(e)});break}this.lineMetadata.set(t,i)}}computeVisibleLines(){this.visibleLines=[];for(let e=0;e<this.lines.length;e++){const t=this.lineMetadata.get(e);t&&t.isCollapsed||this.visibleLines.push({index:e,content:this.lines[e],meta:t})}this._lastStartIndex=-1,this._lastEndIndex=-1,this._lastTotalLines=-1}scheduleRender(){this.renderTimer||(this.renderTimer=requestAnimationFrame(()=>{this.renderTimer=null,this.renderViewport()}))}renderViewport(){const e=this.shadowRoot.getElementById("viewport"),t=this.shadowRoot.getElementById("linesContainer"),s=this.shadowRoot.getElementById("scrollContent");if(this.shadowRoot.getElementById("gutterContent"),!e||!t)return;this.viewportHeight=e.clientHeight;const i=this.visibleLines.length,n=i*this.lineHeight;s&&(s.style.height=`${n}px`);const o=e.scrollTop,r=Math.floor(o/this.lineHeight),l=Math.ceil(this.viewportHeight/this.lineHeight),a=Math.max(0,r-this.bufferLines),c=Math.min(i,r+l+this.bufferLines);if(this._lastStartIndex===a&&this._lastEndIndex===c&&this._lastTotalLines===i)return;this._lastStartIndex=a,this._lastEndIndex=c,this._lastTotalLines=i;const h=a*this.lineHeight;t.style.transform=`translateY(${h}px)`;const d=this._buildContextMap(),u=this.shadowRoot.querySelector(".editor-wrapper"),p=u?.classList.contains("focused"),g=document.createDocumentFragment();for(let f=a;f<c;f++){const e=this.visibleLines[f];if(!e)continue;const t=document.createElement("div");t.className="line",t.dataset.lineIndex=e.index,e.meta?.visibilityButton&&(t.classList.add("has-visibility"),t.dataset.featureKey=e.meta.visibilityButton.featureKey,e.meta.visibilityButton.isHidden&&t.classList.add("feature-hidden")),e.meta?.isHidden&&t.classList.add("line-hidden");const s=d.get(e.index);let i=this._highlightSyntax(e.content,s,e.meta);p&&this._hasSelection()&&(i=this._addSelectionHighlight(i,e.index,e.content)),p&&e.index===this.cursorLine&&(i+=this._insertCursor(this.cursorColumn)),t.innerHTML=i,g.appendChild(t)}t.innerHTML="",t.appendChild(g),this.renderGutter(a,c)}_insertCursor(e){return`<span class="cursor" style="left: ${e*this._getCharWidth()}px"></span>`}_addSelectionHighlight(e,t,s){const{start:i,end:n}=this._normalizeSelection();if(!i||!n)return e;if(t<i.line||t>n.line)return e;const o=this._getCharWidth();let r,l;return t===i.line&&t===n.line?(r=i.column,l=n.column):t===i.line?(r=i.column,l=s.length):t===n.line?(r=0,l=n.column):(r=0,l=s.length),`<span class="selection" style="left: ${r*o}px; width: ${(l-r)*o}px"></span>`+e}_getCharWidth(){if(!this._charWidth){const e=document.createElement("canvas").getContext("2d");e.font="13px monospace",this._charWidth=e.measureText("M").width}return this._charWidth}renderGutter(e,t){const s=this.shadowRoot.getElementById("gutterContent"),i=this.shadowRoot.getElementById("gutterScrollContent");if(!s)return;const n=this.visibleLines.length*this.lineHeight;i&&(i.style.height=`${n}px`);const o=e*this.lineHeight;s.style.transform=`translateY(${o}px)`;const r=document.createDocumentFragment();for(let l=e;l<t;l++){const e=this.visibleLines[l];if(!e)continue;const t=document.createElement("div");t.className="gutter-line";const s=e.meta,i=document.createElement("span");i.className="line-number",i.textContent=e.index+1,t.appendChild(i);const n=document.createElement("div");if(n.className="collapse-column",s?.collapseButton){const t=document.createElement("div");t.className="collapse-button"+(s.collapseButton.isCollapsed?" collapsed":""),t.textContent=s.collapseButton.isCollapsed?"›":"⌄",t.dataset.line=e.index,t.dataset.nodeId=s.collapseButton.nodeId,t.title=s.collapseButton.isCollapsed?"Expand":"Collapse",n.appendChild(t)}t.appendChild(n),r.appendChild(t)}s.innerHTML="",s.appendChild(r)}syncGutterScroll(){const e=this.shadowRoot.getElementById("gutterScroll"),t=this.shadowRoot.getElementById("viewport");e&&t&&(e.scrollTop=t.scrollTop)}handleInput(){const e=this.shadowRoot.getElementById("hiddenTextarea"),t=e.value;if(t)if(this._getCollapsedRangeForLine(this.cursorLine))e.value="";else{if(this._getCollapsedClosingLine(this.cursorLine)){const t=this.lines[this.cursorLine],s=this._getClosingBracketPos(t);if(this.cursorColumn<=s)return void(e.value="")}if(this._getCollapsedNodeAtLine(this.cursorLine)){const t=this.lines[this.cursorLine].search(/[{\[]/);if(this.cursorColumn>t)return void(e.value="")}if(this.cursorLine<this.lines.length){const e=this.lines[this.cursorLine],s=e.substring(0,this.cursorColumn),i=e.substring(this.cursorColumn),n=t.split("\n");if(1===n.length)this.lines[this.cursorLine]=s+t+i,this.cursorColumn+=t.length;else{this.lines[this.cursorLine]=s+n[0];for(let t=1;t<n.length-1;t++)this.lines.splice(this.cursorLine+t,0,n[t]);const e=n[n.length-1]+i;this.lines.splice(this.cursorLine+n.length-1,0,e),this.cursorLine+=n.length-1,this.cursorColumn=n[n.length-1].length}}else{const e=t.split("\n");this.lines.push(...e),this.cursorLine=this.lines.length-1,this.cursorColumn=this.lines[this.cursorLine].length}e.value="",clearTimeout(this.inputTimer),this.inputTimer=setTimeout(()=>{this.formatAndUpdate()},150)}}handleKeydown(e){const t=this._getCollapsedRangeForLine(this.cursorLine),s=this._getCollapsedNodeAtLine(this.cursorLine),i=this._getCollapsedClosingLine(this.cursorLine);switch(e.key){case"Enter":if(e.preventDefault(),s||t)return;if(i){const e=this.lines[this.cursorLine],t=this._getClosingBracketPos(e);if(t>=0&&this.cursorColumn<=t)return}this.insertNewline();break;case"Backspace":if(e.preventDefault(),this._hasSelection())return this._deleteSelection(),void this.formatAndUpdate();if(i){const e=this.lines[this.cursorLine],t=this._getClosingBracketPos(e);return t>=0&&this.cursorColumn>t+1?void this.deleteBackward():(this.cursorColumn,void this._deleteCollapsedNode(i))}if(s&&0===this.cursorColumn)return void this._deleteCollapsedNode(s);if(t)return;if(s){const e=this.lines[this.cursorLine].search(/[{\[]/);if(this.cursorColumn>e+1)return void this._deleteCollapsedNode(s)}this.deleteBackward();break;case"Delete":if(e.preventDefault(),this._hasSelection())return this._deleteSelection(),void this.formatAndUpdate();if(i){const e=this.lines[this.cursorLine],t=this._getClosingBracketPos(e);return t>=0&&this.cursorColumn>t?void this.deleteForward():void this._deleteCollapsedNode(i)}if(s){const e=this.lines[this.cursorLine].search(/[{\[]/);if(this.cursorColumn>e)return void this._deleteCollapsedNode(s)}if(t)return;this.deleteForward();break;case"ArrowUp":e.preventDefault(),this._handleArrowKey(-1,0,e.shiftKey);break;case"ArrowDown":e.preventDefault(),this._handleArrowKey(1,0,e.shiftKey);break;case"ArrowLeft":e.preventDefault(),this._handleArrowKey(0,-1,e.shiftKey);break;case"ArrowRight":e.preventDefault(),this._handleArrowKey(0,1,e.shiftKey);break;case"Home":e.preventDefault(),this._handleHomeEnd("home",e.shiftKey,i);break;case"End":e.preventDefault(),this._handleHomeEnd("end",e.shiftKey,i);break;case"a":if(e.ctrlKey||e.metaKey)return e.preventDefault(),void this._selectAll();break;case"Tab":if(e.preventDefault(),e.shiftKey){const e=this._getContainingExpandedNode(this.cursorLine);if(e){const t=this.lines[e.startLine],s=t.search(/[{\[]/);this.toggleCollapse(e.nodeId),this.cursorLine=e.startLine,this.cursorColumn=s>=0?s+1:t.length,this._clearSelection(),this._scrollToCursor()}return}if(s)return void this.toggleCollapse(s.nodeId);if(i)return void this.toggleCollapse(i.nodeId);if(t)return}}insertNewline(){if(this.cursorLine<this.lines.length){const e=this.lines[this.cursorLine],t=e.substring(0,this.cursorColumn),s=e.substring(this.cursorColumn);this.lines[this.cursorLine]=t,this.lines.splice(this.cursorLine+1,0,s),this.cursorLine++,this.cursorColumn=0}else this.lines.push(""),this.cursorLine=this.lines.length-1,this.cursorColumn=0;this.formatAndUpdate()}deleteBackward(){if(this.cursorColumn>0){const e=this.lines[this.cursorLine];this.lines[this.cursorLine]=e.substring(0,this.cursorColumn-1)+e.substring(this.cursorColumn),this.cursorColumn--}else if(this.cursorLine>0){const e=this.lines[this.cursorLine],t=this.lines[this.cursorLine-1];this.cursorColumn=t.length,this.lines[this.cursorLine-1]=t+e,this.lines.splice(this.cursorLine,1),this.cursorLine--}this.formatAndUpdate()}deleteForward(){if(this.cursorLine<this.lines.length){const e=this.lines[this.cursorLine];this.cursorColumn<e.length?this.lines[this.cursorLine]=e.substring(0,this.cursorColumn)+e.substring(this.cursorColumn+1):this.cursorLine<this.lines.length-1&&(this.lines[this.cursorLine]=e+this.lines[this.cursorLine+1],this.lines.splice(this.cursorLine+1,1))}this.formatAndUpdate()}moveCursorSkipCollapsed(e){let t=this.cursorLine+e;for(;t>=0&&t<this.lines.length;){const s=this._getCollapsedRangeForLine(t);s&&(t=e>0?s.endLine:s.startLine);break}this.cursorLine=Math.max(0,Math.min(this.lines.length-1,t));const s=this.lines[this.cursorLine]?.length||0;this.cursorColumn=Math.min(this.cursorColumn,s),this._lastStartIndex=-1,this._scrollToCursor(),this.scheduleRender()}moveCursorHorizontal(e){const t=this.lines[this.cursorLine],s=this._getCollapsedNodeAtLine(this.cursorLine),i=this._getCollapsedClosingLine(this.cursorLine);if(e>0)if(i){const e=this._getClosingBracketPos(t);this.cursorColumn<e?this.cursorColumn=e:this.cursorColumn>=t.length?this.cursorLine<this.lines.length-1&&(this.cursorLine++,this.cursorColumn=0):this.cursorColumn++}else if(s){const e=t.search(/[{\[]/);if(this.cursorColumn<e)this.cursorColumn++;else if(this.cursorColumn===e)this.cursorColumn=e+1;else{this.cursorLine=s.endLine;const e=this.lines[this.cursorLine];this.cursorColumn=this._getClosingBracketPos(e)}}else if(this.cursorColumn>=t.length){if(this.cursorLine<this.lines.length-1){this.cursorLine++,this.cursorColumn=0;const e=this._getCollapsedRangeForLine(this.cursorLine);e&&(this.cursorLine=e.endLine,this.cursorColumn=0)}}else this.cursorColumn++;else if(i){const e=this._getClosingBracketPos(t);if(this.cursorColumn>e+1)this.cursorColumn--;else if(this.cursorColumn===e+1){this.cursorLine=i.startLine;const e=this.lines[this.cursorLine].search(/[{\[]/);this.cursorColumn=e+1}else{this.cursorLine=i.startLine;const e=this.lines[this.cursorLine].search(/[{\[]/);this.cursorColumn=e+1}}else if(s){const e=t.search(/[{\[]/);this.cursorColumn>e+1?this.cursorColumn=e+1:this.cursorColumn===e+1?this.cursorColumn=e:this.cursorColumn>0?this.cursorColumn--:this.cursorLine>0&&(this.cursorLine--,this.cursorColumn=this.lines[this.cursorLine]?.length||0)}else if(this.cursorColumn>0)this.cursorColumn--;else if(this.cursorLine>0)if(this.cursorLine--,this._getCollapsedClosingLine(this.cursorLine))this.cursorColumn=this.lines[this.cursorLine]?.length||0;else{const e=this._getCollapsedRangeForLine(this.cursorLine);if(e){this.cursorLine=e.startLine;const t=this.lines[this.cursorLine].search(/[{\[]/);this.cursorColumn=t+1}else this.cursorColumn=this.lines[this.cursorLine]?.length||0}this._lastStartIndex=-1,this._scrollToCursor(),this.scheduleRender()}_scrollToCursor(){const e=this.shadowRoot.getElementById("viewport");if(!e)return;const t=this.visibleLines.findIndex(e=>e.index===this.cursorLine);if(-1===t)return;const s=t*this.lineHeight,i=e.scrollTop,n=i+e.clientHeight;s<i?e.scrollTop=s:s+this.lineHeight>n&&(e.scrollTop=s+this.lineHeight-e.clientHeight)}moveCursor(e,t){0!==e?this.moveCursorSkipCollapsed(e):0!==t&&this.moveCursorHorizontal(t)}_handleArrowKey(e,t,s){s&&!this.selectionStart&&(this.selectionStart={line:this.cursorLine,column:this.cursorColumn}),0!==e?this.moveCursorSkipCollapsed(e):0!==t&&this.moveCursorHorizontal(t),s?this.selectionEnd={line:this.cursorLine,column:this.cursorColumn}:(this.selectionStart=null,this.selectionEnd=null)}_handleHomeEnd(e,t,s){t&&!this.selectionStart&&(this.selectionStart={line:this.cursorLine,column:this.cursorColumn}),"home"===e?(s&&(this.cursorLine=s.startLine),this.cursorColumn=0):this.cursorLine<this.lines.length&&(this.cursorColumn=this.lines[this.cursorLine].length),t?this.selectionEnd={line:this.cursorLine,column:this.cursorColumn}:(this.selectionStart=null,this.selectionEnd=null),this._lastStartIndex=-1,this._scrollToCursor(),this.scheduleRender()}_selectAll(){this.selectionStart={line:0,column:0};const e=this.lines.length-1;this.selectionEnd={line:e,column:this.lines[e]?.length||0},this.cursorLine=e,this.cursorColumn=this.lines[e]?.length||0,this._lastStartIndex=-1,this._scrollToCursor(),this.scheduleRender()}_getSelectedText(){if(!this.selectionStart||!this.selectionEnd)return"";const{start:e,end:t}=this._normalizeSelection();if(!e||!t)return"";if(e.line===t.line)return this.lines[e.line].substring(e.column,t.column);let s=this.lines[e.line].substring(e.column)+"\n";for(let i=e.line+1;i<t.line;i++)s+=this.lines[i]+"\n";return s+=this.lines[t.line].substring(0,t.column),s}_normalizeSelection(){if(!this.selectionStart||!this.selectionEnd)return{start:null,end:null};const e=this.selectionStart,t=this.selectionEnd;return e.line<t.line||e.line===t.line&&e.column<=t.column?{start:e,end:t}:{start:t,end:e}}_hasSelection(){return!(!this.selectionStart||!this.selectionEnd||this.selectionStart.line===this.selectionEnd.line&&this.selectionStart.column===this.selectionEnd.column)}_clearSelection(){this.selectionStart=null,this.selectionEnd=null}_deleteSelection(){if(!this._hasSelection())return!1;const{start:e,end:t}=this._normalizeSelection();if(e.line===t.line){const s=this.lines[e.line];this.lines[e.line]=s.substring(0,e.column)+s.substring(t.column)}else{const s=this.lines[e.line].substring(0,e.column),i=this.lines[t.line].substring(t.column);this.lines[e.line]=s+i,this.lines.splice(e.line+1,t.line-e.line)}return this.cursorLine=e.line,this.cursorColumn=e.column,this.selectionStart=null,this.selectionEnd=null,!0}insertText(e){if(this._hasSelection()&&this._deleteSelection(),!this._getCollapsedRangeForLine(this.cursorLine)){if(this._getCollapsedClosingLine(this.cursorLine)){const e=this.lines[this.cursorLine],t=this._getClosingBracketPos(e);if(this.cursorColumn<=t)return}if(this._getCollapsedNodeAtLine(this.cursorLine)){const e=this.lines[this.cursorLine].search(/[{\[]/);if(this.cursorColumn>e)return}if(this.cursorLine<this.lines.length){const t=this.lines[this.cursorLine];this.lines[this.cursorLine]=t.substring(0,this.cursorColumn)+e+t.substring(this.cursorColumn),this.cursorColumn+=e.length}this.formatAndUpdate()}}handlePaste(e){e.preventDefault();const t=e.clipboardData.getData("text/plain");t&&this.insertText(t)}handleCopy(e){e.preventDefault(),this._hasSelection()?e.clipboardData.setData("text/plain",this._getSelectedText()):e.clipboardData.setData("text/plain",this.getContent())}handleCut(e){e.preventDefault(),this._hasSelection()?(e.clipboardData.setData("text/plain",this._getSelectedText()),this._deleteSelection(),this.formatAndUpdate()):(e.clipboardData.setData("text/plain",this.getContent()),this.lines=[],this.cursorLine=0,this.cursorColumn=0,this.formatAndUpdate())}_getPositionFromClick(e){const t=this.shadowRoot.getElementById("viewport"),s=t.getBoundingClientRect(),i=e.clientY-s.top+t.scrollTop-8,n=e.clientX-s.left-12,o=Math.floor(i/this.lineHeight);let r=0,l=0;if(o>=0&&o<this.visibleLines.length){const e=this.visibleLines[o];r=e.index;const t=this._getCharWidth(),s=Math.round(n/t),i=e.content?.length||0;l=Math.max(0,Math.min(s,i))}return{line:r,column:l}}handleGutterClick(e){const t=e.target.closest(".visibility-button");if(t)this.toggleFeatureVisibility(t.dataset.featureKey);else if(e.target.classList.contains("collapse-button")){const t=e.target.dataset.nodeId;return void this.toggleCollapse(t)}}handleEditorClick(e){const t=e.target.closest(".line.has-visibility");if(t){const s=t.getBoundingClientRect();if(e.clientX-s.left<14)return e.preventDefault(),e.stopPropagation(),void this.toggleFeatureVisibility(t.dataset.featureKey)}if(e.target.classList.contains("json-color")){const t=e.target.getBoundingClientRect(),s=e.clientX-t.left;if(s<0&&s>=-8){e.preventDefault(),e.stopPropagation();const t=e.target.dataset.color,s=e.target.closest(".line");if(s){const i=parseInt(s.dataset.lineIndex),n=this.lines[i].match(/"([\w-]+)"\s*:\s*"#/);n&&this.showColorPicker(e.target,i,t,n[1])}return}}if(e.target.classList.contains("json-boolean")){const t=e.target.getBoundingClientRect(),s=e.clientX-t.left;if(s<0&&s>=-8){e.preventDefault(),e.stopPropagation();const t=e.target.closest(".line");if(t){const e=parseInt(t.dataset.lineIndex),s=this.lines[e].match(/"([\w-]+)"\s*:\s*(true|false)/);if(s){const t="true"===s[2];this.updateBooleanValue(e,!t,s[1])}}return}}}toggleCollapse(e){this.collapsedNodes.has(e)?this.collapsedNodes.delete(e):this.collapsedNodes.add(e),this.updateView(),this._lastStartIndex=-1,this.scheduleRender()}autoCollapseCoordinates(){const e=this._findCollapsibleRanges();for(const t of e)"coordinates"===t.nodeKey&&this.collapsedNodes.add(t.nodeId);this.updateView(),this.scheduleRender()}toggleFeatureVisibility(e){this.hiddenFeatures.has(e)?this.hiddenFeatures.delete(e):this.hiddenFeatures.add(e),this.updateView(),this.scheduleRender(),this.emitChange()}showColorPicker(e,t,s,i){const n=document.querySelector(".geojson-color-picker-anchor");n&&n.remove();const o=document.createElement("div");o.className="geojson-color-picker-anchor";const r=e.getBoundingClientRect();o.style.cssText=`\n position: fixed;\n left: ${r.left-8}px;\n top: ${r.top+r.height}px;\n width: 10px;\n height: 10px;\n z-index: 9998;\n `,document.body.appendChild(o);const l=document.createElement("input");l.type="color",l.value=s,l.className="geojson-color-picker-input",l.style.cssText="\n position: absolute;\n left: 0;\n top: 0;\n width: 10px;\n height: 10px;\n opacity: 0;\n border: none;\n padding: 0;\n cursor: pointer;\n ",o.appendChild(l),l.addEventListener("input",e=>{this.updateColorValue(t,e.target.value,i)});const a=e=>{e.target!==l&&(document.removeEventListener("click",a,!0),o.remove())};l._closeListener=a,setTimeout(()=>{document.addEventListener("click",a,!0)},100),l.focus(),l.click()}updateColorValue(e,t,s){const i=new RegExp(`"${s}"\\s*:\\s*"#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})"`);this.lines[e]=this.lines[e].replace(i,`"${s}": "${t}"`),this.updateView(),this.scheduleRender(),this.emitChange()}updateBooleanValue(e,t,s){const i=new RegExp(`"${s}"\\s*:\\s*(true|false)`);this.lines[e]=this.lines[e].replace(i,`"${s}": ${t}`),this.updateView(),this.scheduleRender(),this.emitChange()}formatAndUpdate(){try{const e="["+this.lines.join("\n")+"]",t=JSON.parse(e),s=JSON.stringify(t,null,2).split("\n");this.lines=s.slice(1,-1)}catch(e){}this.updateModel(),this.scheduleRender(),this.updatePlaceholderVisibility(),this.emitChange()}emitChange(){const e=this.getContent(),t=this.prefix+e+this.suffix;try{let s=JSON.parse(t);this.hiddenFeatures.size>0&&(s.features=s.features.filter(e=>{const t=this._getFeatureKey(e);return!this.hiddenFeatures.has(t)}));const i=this._validateGeoJSON(s);i.length>0?this.dispatchEvent(new CustomEvent("error",{detail:{error:i.join("; "),errors:i,content:e},bubbles:!0,composed:!0})):this.dispatchEvent(new CustomEvent("change",{detail:s,bubbles:!0,composed:!0}))}catch(s){this.dispatchEvent(new CustomEvent("error",{detail:{error:s.message,content:e},bubbles:!0,composed:!0}))}}updateReadonly(){const e=this.shadowRoot.getElementById("hiddenTextarea"),t=this.shadowRoot.getElementById("clearBtn");e&&(e.readOnly=this.readonly),t&&(t.hidden=this.readonly)}updatePlaceholderVisibility(){const e=this.shadowRoot.getElementById("placeholderLayer");e&&(e.style.display=this.lines.length>0?"none":"block")}updatePlaceholderContent(){const e=this.shadowRoot.getElementById("placeholderLayer");e&&(e.textContent=this.placeholder),this.updatePlaceholderVisibility()}updatePrefixSuffix(){const e=this.shadowRoot.getElementById("editorPrefix"),t=this.shadowRoot.getElementById("editorSuffix");e&&(e.textContent=this.prefix),t&&(t.textContent=this.suffix)}updateThemeCSS(){const e=this.getAttribute("dark-selector")||".dark",t=this._parseSelectorToHostRule(e);let s=this.shadowRoot.getElementById("theme-styles");s||(s=document.createElement("style"),s.id="theme-styles",this.shadowRoot.insertBefore(s,this.shadowRoot.firstChild));const i=e=>Object.entries(e).map(([e,t])=>{return`--${s=e,s.replace(/([A-Z])/g,"-$1").toLowerCase()}: ${t};`;var s}).join("\n "),n=i(this.themes.light||{});let o=n?`:host {\n ${n}\n }\n`:"";o+=`${t} {\n ${i({bgColor:"#2b2b2b",textColor:"#a9b7c6",caretColor:"#bbbbbb",gutterBg:"#313335",gutterBorder:"#3c3f41",gutterText:"#606366",jsonKey:"#9876aa",jsonString:"#6a8759",jsonNumber:"#6897bb",jsonBoolean:"#cc7832",jsonNull:"#cc7832",jsonPunct:"#a9b7c6",jsonError:"#ff6b68",controlColor:"#cc7832",controlBg:"#3c3f41",controlBorder:"#5a5a5a",geojsonKey:"#9876aa",geojsonType:"#6a8759",geojsonTypeInvalid:"#ff6b68",jsonKeyInvalid:"#ff6b68",...this.themes.dark})}\n }`,s.textContent=o}_parseSelectorToHostRule(e){return e?e.startsWith(".")&&!e.includes(" ")?`:host(${e})`:`:host-context(${e})`:':host([data-color-scheme="dark"])'}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:{},light:{}},this.updateThemeCSS()}_getFeatureKey(e){if(!e)return null;if(void 0!==e.id)return`id:${e.id}`;if(void 0!==e.properties?.id)return`prop:${e.properties.id}`;const t=e.geometry?.type||"null",s=JSON.stringify(e.geometry?.coordinates||[]);let i=0;for(let n=0;n<s.length;n++)i=(i<<5)-i+s.charCodeAt(n),i&=i;return`hash:${t}:${i.toString(36)}`}_countBrackets(e,t){const s="{"===t?"}":"]";let i=0,n=0,o=!1,r=!1;for(const l of e)r?r=!1:"\\"===l&&o?r=!0:'"'!==l?o||(l===t&&i++,l===s&&n++):o=!o;return{open:i,close:n}}_findCollapsibleRanges(){const e=[];for(const[t,s]of this._lineToNodeId){const i=this._nodeIdToLines.get(s);if(!i)continue;const n=this.lines[t];if(!n)continue;const o=n.match(/^\s*"([^"]+)"\s*:\s*([{\[])/),r=!o&&n.match(/^\s*([{\[]),?\s*$/);if(!o&&!r)continue;const l=o?o[2]:r[1];e.push({startLine:i.startLine,endLine:i.endLine,nodeKey:i.nodeKey||(o?o[1]:`__root_${t}`),nodeId:s,openBracket:l,isRootFeature:!!r})}return e.sort((e,t)=>e.startLine-t.startLine),e}_findClosingLine(e,t){let s=1;const i=this.lines[e],n=i.indexOf(t);if(-1!==n){const o=i.substring(n+1),r=this._countBrackets(o,t);if(s+=r.open-r.close,0===s)return e}for(let o=e+1;o<this.lines.length;o++){const e=this._countBrackets(this.lines[o],t);if(s+=e.open-e.close,0===s)return o}return-1}_buildContextMap(){const e=/* @__PURE__ */new Map,t=[];let s=null;for(let i=0;i<this.lines.length;i++){const n=this.lines[i],o=t[t.length-1]?.context||"Feature";e.set(i,o),/"geometry"\s*:/.test(n)?s="geometry":/"properties"\s*:/.test(n)?s="properties":/"features"\s*:/.test(n)&&(s="Feature");const r=(n.match(/\{/g)||[]).length,l=(n.match(/\}/g)||[]).length,a=(n.match(/\[/g)||[]).length,c=(n.match(/\]/g)||[]).length;for(let e=0;e<r+a;e++)t.push({context:s||o,isArray:e>=r}),s=null;for(let e=0;e<l+c&&t.length>0;e++)t.pop()}return e}_highlightSyntax(s,i,n){if(!s)return"";let o=s,r=null;if(n?.collapseButton?.isCollapsed){const e=s.match(/^(\s*"[^"]+"\s*:\s*)([{\[])/),t=!e&&s.match(/^(\s*)([{\[]),?\s*$/);e?(o=e[1]+e[2],r=e[2]):t&&(o=t[1]+t[2],r=t[2])}let l=o.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");if(l=l.replace(/([{}[\],:])/g,'<span class="json-punctuation">$1</span>'),l=l.replace(/"([^"]+)"(<span class="json-punctuation">:<\/span>)/g,(t,s,n)=>"properties"!==i&&e.includes(s)?`<span class="geojson-key">"${s}"</span>${n}`:`<span class="json-key">"${s}"</span>${n}`),"properties"!==i&&(l=l.replace(/<span class="geojson-key">"type"<\/span><span class="json-punctuation">:<\/span>\s*"([^"]*)"/g,(e,s)=>`<span class="geojson-key">"type"</span><span class="json-punctuation">:</span> <span class="${"Feature"===s||"FeatureCollection"===s||t.includes(s)?"geojson-type":"geojson-type-invalid"}">"${s}"</span>`)),l=l.replace(/(<span class="json-punctuation">:<\/span>)\s*"([^"]*)"/g,(e,t,s)=>e.includes("geojson-type")||e.includes("json-string")?e:/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(s)?`${t} <span class="json-string json-color" data-color="${s}" style="--swatch-color: ${s}">"${s}"</span>`:`${t} <span class="json-string">"${s}"</span>`),l=l.replace(/(<span class="json-punctuation">:<\/span>)\s*(-?\d+\.?\d*(?:e[+-]?\d+)?)/gi,'$1 <span class="json-number">$2</span>'),l=l.replace(/(<span class="json-punctuation">[\[,]<\/span>)\s*(-?\d+\.?\d*(?:e[+-]?\d+)?)/gi,'$1<span class="json-number">$2</span>'),l=l.replace(/^(\s*)(-?\d+\.?\d*(?:e[+-]?\d+)?)/gim,'$1<span class="json-number">$2</span>'),l=l.replace(/(<span class="json-punctuation">:<\/span>)\s*(true|false)/g,(e,t,s)=>`${t} <span class="json-boolean${"true"===s?" json-bool-true":" json-bool-false"}">${s}</span>`),l=l.replace(/(<span class="json-punctuation">:<\/span>)\s*(null)/g,'$1 <span class="json-null">$2</span>'),r){const e="["===r?"collapsed-bracket-array":"collapsed-bracket-object";l=l.replace(new RegExp(`<span class="json-punctuation">\\${r}<\\/span>$`),`<span class="${e}">${r}</span>`)}return l=l.replace(/(<\/span>|^)([^<]+)(<span|$)/g,(e,t,s,i)=>{if(!s||/^\s*$/.test(s))return e;const n=s.split(/(\s+)/);let o=!1;const r=n.map(e=>/^\s*$/.test(e)?e:(o=!0,`<span class="json-error">${e}</span>`)).join("");return o?t+r+i:e}),l}_validateGeoJSON(e){const s=[];return e.features?(e.features.forEach((e,i)=>{"Feature"!==e.type&&s.push(`features[${i}]: type must be "Feature"`),e.geometry&&e.geometry.type&&(t.includes(e.geometry.type)||s.push(`features[${i}].geometry: invalid type "${e.geometry.type}"`))}),s):s}set(e){if(!Array.isArray(e))throw new Error("set() expects an array");const t=e.map(e=>JSON.stringify(e,null,2)).join(",\n");this.setValue(t)}add(e){const t=this._parseFeatures();t.push(e),this.set(t)}insertAt(e,t){const s=this._parseFeatures(),i=t<0?s.length+t:t;s.splice(Math.max(0,Math.min(i,s.length)),0,e),this.set(s)}removeAt(e){const t=this._parseFeatures(),s=e<0?t.length+e:e;if(s>=0&&s<t.length){const e=t.splice(s,1)[0];return this.set(t),e}}removeAll(){const e=this._parseFeatures();return this.lines=[],this.collapsedNodes.clear(),this.hiddenFeatures.clear(),this.updateModel(),this.scheduleRender(),this.updatePlaceholderVisibility(),this.emitChange(),e}get(e){const t=this._parseFeatures();return t[e<0?t.length+e:e]}getAll(){return this._parseFeatures()}emit(){this.emitChange()}_parseFeatures(){try{const e=this.lines.join("\n");return e.trim()?JSON.parse("["+e+"]"):[]}catch(e){return[]}}}customElements.define("geojson-editor",s);export{s as default};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softwarity/geojson-editor",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "description": "A feature-rich GeoJSON editor Web Component with syntax highlighting, collapsible nodes, and color picker",
5
5
  "type": "module",
6
6
  "main": "./dist/geojson-editor.js",
@@ -42,6 +42,7 @@
42
42
  "devDependencies": {
43
43
  "@esm-bundle/chai": "^4.3.4-fix.0",
44
44
  "@open-wc/testing": "^4.0.0",
45
+ "@web/dev-server-esbuild": "^1.0.4",
45
46
  "@web/test-runner": "^0.20.2",
46
47
  "@web/test-runner-playwright": "^0.11.1",
47
48
  "minify-literals": "^1.0.10",