@softwarity/geojson-editor 1.0.23 → 1.0.25
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 +168 -32
- package/dist/geojson-editor.js +2 -2
- package/package.json +9 -3
- package/src/constants.ts +0 -3
- package/src/geojson-editor.css +48 -43
- package/src/geojson-editor.ts +91 -110
- package/src/types.ts +0 -30
- package/themes/github.css +43 -0
- package/themes/monokai.css +43 -0
- package/themes/solarized.css +44 -0
- package/themes/vscode.css +43 -0
- package/types/types.d.ts +0 -28
package/src/geojson-editor.css
CHANGED
|
@@ -11,10 +11,15 @@
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
:host {
|
|
14
|
+
/* Color scheme - inherits from parent, falls back to system preference if not set */
|
|
15
|
+
color-scheme: inherit;
|
|
16
|
+
|
|
17
|
+
/* Layout variables (internal only) */
|
|
14
18
|
--line-height: 19.5px;
|
|
15
19
|
--gutter-width: 50px;
|
|
16
20
|
--editor-padding-y: 8px;
|
|
17
21
|
--editor-padding-x: 12px;
|
|
22
|
+
|
|
18
23
|
display: flex;
|
|
19
24
|
flex-direction: column;
|
|
20
25
|
position: relative;
|
|
@@ -39,7 +44,7 @@
|
|
|
39
44
|
position: relative;
|
|
40
45
|
width: 100%;
|
|
41
46
|
flex: 1;
|
|
42
|
-
background: var(--bg-color, #fff);
|
|
47
|
+
background: var(--geojson-editor-bg-color, light-dark(#fff, #2b2b2b));
|
|
43
48
|
display: flex;
|
|
44
49
|
overflow: hidden;
|
|
45
50
|
}
|
|
@@ -47,8 +52,8 @@
|
|
|
47
52
|
/* ========== Gutter ========== */
|
|
48
53
|
.gutter {
|
|
49
54
|
width: var(--gutter-width);
|
|
50
|
-
background: var(--gutter-bg, #f0f0f0);
|
|
51
|
-
border-right: 1px solid var(--gutter-border, #e0e0e0);
|
|
55
|
+
background: var(--geojson-editor-gutter-bg, light-dark(#f0f0f0, #313335));
|
|
56
|
+
border-right: 1px solid var(--geojson-editor-gutter-border, light-dark(#e0e0e0, #3c3f41));
|
|
52
57
|
overflow: hidden;
|
|
53
58
|
flex-shrink: 0;
|
|
54
59
|
position: relative;
|
|
@@ -90,12 +95,12 @@
|
|
|
90
95
|
top: 0;
|
|
91
96
|
bottom: 0;
|
|
92
97
|
width: 3px;
|
|
93
|
-
background: var(--error-color, #dc3545);
|
|
98
|
+
background: var(--geojson-editor-error-color, light-dark(#dc3545, #ff6b68));
|
|
94
99
|
}
|
|
95
100
|
|
|
96
101
|
.line-number {
|
|
97
102
|
font-size: 11px;
|
|
98
|
-
color: var(--gutter-text, #999);
|
|
103
|
+
color: var(--geojson-editor-gutter-text, light-dark(#999, #606366));
|
|
99
104
|
user-select: none;
|
|
100
105
|
min-width: 20px;
|
|
101
106
|
text-align: right;
|
|
@@ -118,7 +123,7 @@
|
|
|
118
123
|
flex-shrink: 0;
|
|
119
124
|
background: transparent;
|
|
120
125
|
border: none;
|
|
121
|
-
color: var(--json-punct, #a9b7c6);
|
|
126
|
+
color: var(--geojson-editor-json-punct, light-dark(#000, #a9b7c6));
|
|
122
127
|
font-size: 10px;
|
|
123
128
|
display: flex;
|
|
124
129
|
align-items: center;
|
|
@@ -178,7 +183,7 @@
|
|
|
178
183
|
padding: var(--editor-padding-y) var(--editor-padding-x);
|
|
179
184
|
overscroll-behavior: contain; /* Prevent scroll chaining to parent */
|
|
180
185
|
scrollbar-width: thin;
|
|
181
|
-
scrollbar-color: var(--control-border, #c0c0c0) var(--control-bg, #e8e8e8);
|
|
186
|
+
scrollbar-color: var(--geojson-editor-control-border, light-dark(#c0c0c0, #5a5a5a)) var(--geojson-editor-control-bg, light-dark(#e8e8e8, #3c3f41));
|
|
182
187
|
}
|
|
183
188
|
|
|
184
189
|
/* Scroll content - defines total scrollable height */
|
|
@@ -209,7 +214,7 @@
|
|
|
209
214
|
position: absolute;
|
|
210
215
|
width: 2px;
|
|
211
216
|
height: 1em;
|
|
212
|
-
background: var(--caret-color, #000);
|
|
217
|
+
background: var(--geojson-editor-caret-color, light-dark(#000, #bbb));
|
|
213
218
|
top: 0.15em;
|
|
214
219
|
pointer-events: none;
|
|
215
220
|
animation: cursor-blink 1s step-end infinite;
|
|
@@ -237,7 +242,7 @@
|
|
|
237
242
|
position: absolute;
|
|
238
243
|
height: 100%;
|
|
239
244
|
top: 0;
|
|
240
|
-
background: var(--selection-color, rgba(51, 153, 255, 0.3));
|
|
245
|
+
background: var(--geojson-editor-selection-color, light-dark(rgba(51, 153, 255, 0.3), rgba(51, 153, 255, 0.4)));
|
|
241
246
|
pointer-events: none;
|
|
242
247
|
z-index: 0;
|
|
243
248
|
}
|
|
@@ -259,47 +264,47 @@
|
|
|
259
264
|
}
|
|
260
265
|
|
|
261
266
|
/* ========== Syntax Highlighting ========== */
|
|
262
|
-
.json-key { color: var(--json-key, #660e7a); }
|
|
263
|
-
.json-string { color: var(--json-string, #008000); }
|
|
264
|
-
.json-number { color: var(--json-number, #00f); }
|
|
265
|
-
.json-boolean, .json-null { color: var(--json-boolean, #000080); }
|
|
266
|
-
.json-punctuation { color: var(--json-punct, #000); }
|
|
267
|
-
.json-error { color: var(--json-error, #f00); }
|
|
267
|
+
.json-key { color: var(--geojson-editor-json-key, light-dark(#660e7a, #9876aa)); }
|
|
268
|
+
.json-string { color: var(--geojson-editor-json-string, light-dark(#008000, #6a8759)); }
|
|
269
|
+
.json-number { color: var(--geojson-editor-json-number, light-dark(#00f, #6897bb)); }
|
|
270
|
+
.json-boolean, .json-null { color: var(--geojson-editor-json-boolean, light-dark(#000080, #cc7832)); }
|
|
271
|
+
.json-punctuation { color: var(--geojson-editor-json-punct, light-dark(#000, #a9b7c6)); }
|
|
272
|
+
.json-error { color: var(--geojson-editor-json-error, light-dark(#f00, #ff6b68)); }
|
|
268
273
|
|
|
269
|
-
.geojson-key { color: var(--geojson-key, #660e7a); font-weight: 600; }
|
|
270
|
-
.geojson-type { color: var(--geojson-type, #008000); font-weight: 600; }
|
|
271
|
-
.geojson-type-invalid { color: var(--geojson-type-invalid, #f00); font-weight: 600; }
|
|
274
|
+
.geojson-key { color: var(--geojson-editor-geojson-key, light-dark(#660e7a, #9876aa)); font-weight: 600; }
|
|
275
|
+
.geojson-type { color: var(--geojson-editor-geojson-type, light-dark(#008000, #6a8759)); font-weight: 600; }
|
|
276
|
+
.geojson-type-invalid { color: var(--geojson-editor-geojson-type-invalid, light-dark(#f00, #ff6b68)); font-weight: 600; }
|
|
272
277
|
|
|
273
278
|
/* Collapsed node styling */
|
|
274
279
|
.collapsed-bracket-array,
|
|
275
280
|
.collapsed-bracket-object {
|
|
276
|
-
color: var(--json-punct, #000);
|
|
281
|
+
color: var(--geojson-editor-json-punct, light-dark(#000, #a9b7c6));
|
|
277
282
|
}
|
|
278
283
|
.collapsed-bracket-array::after,
|
|
279
284
|
.collapsed-bracket-object::after {
|
|
280
285
|
content: '…';
|
|
281
|
-
color: var(--json-punct, #
|
|
286
|
+
color: var(--geojson-editor-json-punct, light-dark(#000, #a9b7c6));
|
|
282
287
|
}
|
|
283
288
|
|
|
284
289
|
/* ========== Prefix/Suffix ========== */
|
|
285
290
|
.prefix-wrapper, .suffix-wrapper {
|
|
286
291
|
display: flex;
|
|
287
292
|
flex-shrink: 0;
|
|
288
|
-
background: var(--bg-color, #fff);
|
|
293
|
+
background: var(--geojson-editor-bg-color, light-dark(#fff, #2b2b2b));
|
|
289
294
|
}
|
|
290
295
|
|
|
291
296
|
.prefix-gutter, .suffix-gutter {
|
|
292
297
|
width: var(--gutter-width);
|
|
293
|
-
background: var(--gutter-bg, #f0f0f0);
|
|
294
|
-
border-right: 1px solid var(--gutter-border, #e0e0e0);
|
|
298
|
+
background: var(--geojson-editor-gutter-bg, light-dark(#f0f0f0, #313335));
|
|
299
|
+
border-right: 1px solid var(--geojson-editor-gutter-border, light-dark(#e0e0e0, #3c3f41));
|
|
295
300
|
flex-shrink: 0;
|
|
296
301
|
}
|
|
297
302
|
|
|
298
303
|
.editor-prefix, .editor-suffix {
|
|
299
304
|
flex: 1;
|
|
300
305
|
padding: 4px 12px;
|
|
301
|
-
color: var(--text-color, #000);
|
|
302
|
-
background: var(--bg-color, #fff);
|
|
306
|
+
color: var(--geojson-editor-text-color, light-dark(#000, #a9b7c6));
|
|
307
|
+
background: var(--geojson-editor-bg-color, light-dark(#fff, #2b2b2b));
|
|
303
308
|
user-select: none;
|
|
304
309
|
white-space: pre-wrap;
|
|
305
310
|
word-wrap: break-word;
|
|
@@ -316,7 +321,7 @@
|
|
|
316
321
|
transform: translateY(-50%);
|
|
317
322
|
background: transparent;
|
|
318
323
|
border: none;
|
|
319
|
-
color: var(--text-color, #000);
|
|
324
|
+
color: var(--geojson-editor-text-color, light-dark(#000, #a9b7c6));
|
|
320
325
|
opacity: 0.15;
|
|
321
326
|
cursor: pointer;
|
|
322
327
|
font-size: 0.7rem;
|
|
@@ -339,8 +344,8 @@
|
|
|
339
344
|
top: 2rem;
|
|
340
345
|
right: 0.5rem;
|
|
341
346
|
z-index: 1000;
|
|
342
|
-
background: var(--bg-color, #
|
|
343
|
-
border: 1px solid var(--gutter-border, #e0e0e0);
|
|
347
|
+
background: var(--geojson-editor-bg-color, light-dark(#fff, #2b2b2b));
|
|
348
|
+
border: 1px solid var(--geojson-editor-gutter-border, light-dark(#e0e0e0, #3c3f41));
|
|
344
349
|
border-radius: 6px;
|
|
345
350
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
346
351
|
padding: 12px 16px;
|
|
@@ -356,13 +361,13 @@
|
|
|
356
361
|
.info-popup-title {
|
|
357
362
|
font-weight: bold;
|
|
358
363
|
font-size: 13px;
|
|
359
|
-
color: var(--text-color, #
|
|
364
|
+
color: var(--geojson-editor-text-color, light-dark(#000, #a9b7c6));
|
|
360
365
|
margin-bottom: 4px;
|
|
361
366
|
}
|
|
362
367
|
.info-popup-version {
|
|
363
368
|
display: block;
|
|
364
369
|
font-size: 11px;
|
|
365
|
-
color: var(--text-color, #
|
|
370
|
+
color: var(--geojson-editor-text-color, light-dark(#000, #a9b7c6));
|
|
366
371
|
opacity: 0.6;
|
|
367
372
|
margin-bottom: 8px;
|
|
368
373
|
text-decoration: none;
|
|
@@ -373,7 +378,7 @@
|
|
|
373
378
|
}
|
|
374
379
|
.info-popup-copyright {
|
|
375
380
|
font-size: 10px;
|
|
376
|
-
color: var(--text-color, #
|
|
381
|
+
color: var(--geojson-editor-text-color, light-dark(#000, #a9b7c6));
|
|
377
382
|
opacity: 0.5;
|
|
378
383
|
}
|
|
379
384
|
|
|
@@ -384,7 +389,7 @@
|
|
|
384
389
|
transform: translateY(-50%);
|
|
385
390
|
background: transparent;
|
|
386
391
|
border: none;
|
|
387
|
-
color: var(--text-color, #000);
|
|
392
|
+
color: var(--geojson-editor-text-color, light-dark(#000, #a9b7c6));
|
|
388
393
|
opacity: 0.3;
|
|
389
394
|
cursor: pointer;
|
|
390
395
|
font-size: 0.65rem;
|
|
@@ -413,7 +418,7 @@
|
|
|
413
418
|
.error-nav-btn {
|
|
414
419
|
background: transparent;
|
|
415
420
|
border: none;
|
|
416
|
-
color: var(--error-color, #dc3545);
|
|
421
|
+
color: var(--geojson-editor-error-color, light-dark(#dc3545, #ff6b68));
|
|
417
422
|
cursor: pointer;
|
|
418
423
|
font-size: 8px;
|
|
419
424
|
width: 16px;
|
|
@@ -429,7 +434,7 @@
|
|
|
429
434
|
opacity: 1;
|
|
430
435
|
}
|
|
431
436
|
.error-count {
|
|
432
|
-
color: var(--error-color, #dc3545);
|
|
437
|
+
color: var(--geojson-editor-error-color, light-dark(#dc3545, #ff6b68));
|
|
433
438
|
font-size: 11px;
|
|
434
439
|
min-width: 20px;
|
|
435
440
|
text-align: center;
|
|
@@ -437,9 +442,9 @@
|
|
|
437
442
|
|
|
438
443
|
/* ========== Scrollbar ========== */
|
|
439
444
|
.viewport::-webkit-scrollbar { width: 10px; height: 10px; }
|
|
440
|
-
.viewport::-webkit-scrollbar-track { background: var(--control-bg, #e8e8e8); }
|
|
441
|
-
.viewport::-webkit-scrollbar-thumb { background: var(--control-border, #c0c0c0); border-radius: 5px; }
|
|
442
|
-
.viewport::-webkit-scrollbar-thumb:hover { background: var(--control-color, #000080); }
|
|
445
|
+
.viewport::-webkit-scrollbar-track { background: var(--geojson-editor-control-bg, light-dark(#e8e8e8, #3c3f41)); }
|
|
446
|
+
.viewport::-webkit-scrollbar-thumb { background: var(--geojson-editor-control-border, light-dark(#c0c0c0, #5a5a5a)); border-radius: 5px; }
|
|
447
|
+
.viewport::-webkit-scrollbar-thumb:hover { background: var(--geojson-editor-control-color, light-dark(#000080, #cc7832)); }
|
|
443
448
|
|
|
444
449
|
/* ========== Inline Controls (using ::before pseudo-elements) ========== */
|
|
445
450
|
|
|
@@ -464,24 +469,24 @@
|
|
|
464
469
|
.json-color:hover::before,
|
|
465
470
|
.json-boolean:hover::before {
|
|
466
471
|
transform: translateY(-50%) scale(1.2);
|
|
467
|
-
border-color: var(--control-color, #000080);
|
|
472
|
+
border-color: var(--geojson-editor-control-color, light-dark(#000080, #cc7832));
|
|
468
473
|
}
|
|
469
474
|
|
|
470
475
|
/* Color swatch specific styles */
|
|
471
476
|
.json-color::before {
|
|
472
477
|
background-color: var(--swatch-color);
|
|
473
|
-
border: 1px solid var(--json-punct, #a9b7c6);
|
|
478
|
+
border: 1px solid var(--geojson-editor-json-punct, light-dark(#000, #a9b7c6));
|
|
474
479
|
}
|
|
475
480
|
|
|
476
481
|
/* Boolean checkbox specific styles */
|
|
477
482
|
.json-boolean::before {
|
|
478
|
-
border: 1.5px solid var(--control-border, #c0c0c0);
|
|
483
|
+
border: 1.5px solid var(--geojson-editor-control-border, light-dark(#c0c0c0, #5a5a5a));
|
|
479
484
|
background: transparent;
|
|
480
485
|
}
|
|
481
486
|
.json-bool-true::before {
|
|
482
487
|
content: '✔';
|
|
483
|
-
border-color: var(--control-color, #000080);
|
|
484
|
-
color: var(--control-color, #000080);
|
|
488
|
+
border-color: var(--geojson-editor-control-color, light-dark(#000080, #cc7832));
|
|
489
|
+
color: var(--geojson-editor-control-color, light-dark(#000080, #cc7832));
|
|
485
490
|
font-size: 8px;
|
|
486
491
|
display: flex;
|
|
487
492
|
align-items: center;
|
|
@@ -496,7 +501,7 @@
|
|
|
496
501
|
left: 0;
|
|
497
502
|
top: 3px;
|
|
498
503
|
font-size: 10px;
|
|
499
|
-
color: var(--control-color, #000080);
|
|
504
|
+
color: var(--geojson-editor-control-color, light-dark(#000080, #cc7832));
|
|
500
505
|
opacity: 0.6;
|
|
501
506
|
cursor: pointer;
|
|
502
507
|
z-index: 1;
|
package/src/geojson-editor.ts
CHANGED
|
@@ -4,8 +4,7 @@ import type { Feature } from 'geojson';
|
|
|
4
4
|
|
|
5
5
|
// ========== Imports from extracted modules ==========
|
|
6
6
|
import type {
|
|
7
|
-
SetOptions
|
|
8
|
-
ThemeSettings
|
|
7
|
+
SetOptions
|
|
9
8
|
} from './types.js';
|
|
10
9
|
|
|
11
10
|
import type {
|
|
@@ -35,19 +34,18 @@ import {
|
|
|
35
34
|
RE_BRACKET_POS,
|
|
36
35
|
RE_IS_WORD_CHAR,
|
|
37
36
|
RE_ATTR_AND_BOOL_VALUE,
|
|
38
|
-
RE_TO_KEBAB,
|
|
39
37
|
RE_OPEN_BRACES,
|
|
40
38
|
RE_CLOSE_BRACES,
|
|
41
39
|
RE_OPEN_BRACKETS,
|
|
42
40
|
RE_CLOSE_BRACKET
|
|
43
41
|
} from './constants.js';
|
|
44
42
|
|
|
45
|
-
import { createElement, countBrackets
|
|
43
|
+
import { createElement, countBrackets } from './utils.js';
|
|
46
44
|
import { validateGeoJSON, normalizeToFeatures } from './validation.js';
|
|
47
45
|
import { highlightSyntax, namedColorToHex, isNamedColor } from './syntax-highlighter.js';
|
|
48
46
|
|
|
49
47
|
// Re-export public types
|
|
50
|
-
export type { SetOptions
|
|
48
|
+
export type { SetOptions } from './types.js';
|
|
51
49
|
|
|
52
50
|
// Alias for minification
|
|
53
51
|
const _ce = createElement;
|
|
@@ -94,8 +92,6 @@ class GeoJsonEditor extends HTMLElement {
|
|
|
94
92
|
private renderTimer: ReturnType<typeof setTimeout> | undefined = undefined;
|
|
95
93
|
private inputTimer: ReturnType<typeof setTimeout> | undefined = undefined;
|
|
96
94
|
|
|
97
|
-
// ========== Theme ==========
|
|
98
|
-
themes: ThemeSettings = { dark: {}, light: {} };
|
|
99
95
|
|
|
100
96
|
// ========== Undo/Redo History ==========
|
|
101
97
|
private _undoStack: EditorSnapshot[] = [];
|
|
@@ -116,6 +112,7 @@ class GeoJsonEditor extends HTMLElement {
|
|
|
116
112
|
private _contextMapFirstLine: string | undefined = undefined;
|
|
117
113
|
private _contextMapLastLine: string | undefined = undefined;
|
|
118
114
|
private _errorLinesCache: Set<number> | null = null;
|
|
115
|
+
private _lastCursorFeatureIndex: number | null = null; // For current-feature event deduplication
|
|
119
116
|
|
|
120
117
|
// ========== Cached DOM Elements ==========
|
|
121
118
|
private _viewport: HTMLElement | null = null;
|
|
@@ -440,7 +437,7 @@ class GeoJsonEditor extends HTMLElement {
|
|
|
440
437
|
|
|
441
438
|
// ========== Observed Attributes ==========
|
|
442
439
|
static get observedAttributes() {
|
|
443
|
-
return ['readonly', 'value', 'placeholder', '
|
|
440
|
+
return ['readonly', 'value', 'placeholder', 'internal-add-shortcut'];
|
|
444
441
|
}
|
|
445
442
|
|
|
446
443
|
// ========== Lifecycle ==========
|
|
@@ -449,7 +446,6 @@ class GeoJsonEditor extends HTMLElement {
|
|
|
449
446
|
this._cacheElements();
|
|
450
447
|
this.setupEventListeners();
|
|
451
448
|
this.updatePrefixSuffix();
|
|
452
|
-
this.updateThemeCSS();
|
|
453
449
|
|
|
454
450
|
if (this.value) {
|
|
455
451
|
this.setValue(this.value);
|
|
@@ -484,9 +480,6 @@ class GeoJsonEditor extends HTMLElement {
|
|
|
484
480
|
case 'placeholder':
|
|
485
481
|
this.updatePlaceholderContent();
|
|
486
482
|
break;
|
|
487
|
-
case 'dark-selector':
|
|
488
|
-
this.updateThemeCSS();
|
|
489
|
-
break;
|
|
490
483
|
}
|
|
491
484
|
}
|
|
492
485
|
|
|
@@ -697,12 +690,16 @@ class GeoJsonEditor extends HTMLElement {
|
|
|
697
690
|
editorWrapper.classList.add('focused');
|
|
698
691
|
this._invalidateRenderCache(); // Force re-render to show cursor
|
|
699
692
|
this.scheduleRender();
|
|
693
|
+
// Emit current feature on focus (force to always emit on focus gain)
|
|
694
|
+
this._emitCurrentFeature(true);
|
|
700
695
|
});
|
|
701
696
|
|
|
702
697
|
hiddenTextarea.addEventListener('blur', () => {
|
|
703
698
|
editorWrapper.classList.remove('focused');
|
|
704
699
|
this._invalidateRenderCache(); // Force re-render to hide cursor
|
|
705
700
|
this.scheduleRender();
|
|
701
|
+
// Emit null on blur
|
|
702
|
+
this._emitCurrentFeatureNull();
|
|
706
703
|
});
|
|
707
704
|
|
|
708
705
|
// Scroll handling
|
|
@@ -1324,11 +1321,16 @@ class GeoJsonEditor extends HTMLElement {
|
|
|
1324
1321
|
|
|
1325
1322
|
linesContainer.innerHTML = '';
|
|
1326
1323
|
linesContainer.appendChild(fragment);
|
|
1327
|
-
|
|
1324
|
+
|
|
1328
1325
|
// Render gutter with same range
|
|
1329
1326
|
this.renderGutter(startIndex, endIndex);
|
|
1327
|
+
|
|
1328
|
+
// Emit current-feature event if feature changed (only when editor is focused)
|
|
1329
|
+
if (this._editorWrapper?.classList.contains('focused')) {
|
|
1330
|
+
this._emitCurrentFeature();
|
|
1331
|
+
}
|
|
1330
1332
|
}
|
|
1331
|
-
|
|
1333
|
+
|
|
1332
1334
|
/**
|
|
1333
1335
|
* Insert cursor element at the specified column position
|
|
1334
1336
|
* Uses absolute positioning to avoid affecting text layout
|
|
@@ -2757,7 +2759,14 @@ class GeoJsonEditor extends HTMLElement {
|
|
|
2757
2759
|
// Try to parse as GeoJSON and normalize
|
|
2758
2760
|
let pastedFeatureCount = 0;
|
|
2759
2761
|
try {
|
|
2760
|
-
|
|
2762
|
+
// First try direct parse (single Feature, Feature[], or FeatureCollection)
|
|
2763
|
+
let parsed;
|
|
2764
|
+
try {
|
|
2765
|
+
parsed = JSON.parse(text);
|
|
2766
|
+
} catch {
|
|
2767
|
+
// If direct parse fails, try wrapping with [] (for "feature, feature" format from editor copy)
|
|
2768
|
+
parsed = JSON.parse('[' + text + ']');
|
|
2769
|
+
}
|
|
2761
2770
|
const features = normalizeToFeatures(parsed);
|
|
2762
2771
|
pastedFeatureCount = features.length;
|
|
2763
2772
|
// Valid GeoJSON - insert formatted features
|
|
@@ -2780,13 +2789,11 @@ class GeoJsonEditor extends HTMLElement {
|
|
|
2780
2789
|
if (pastedFeatureCount > 0) {
|
|
2781
2790
|
// Restore collapsed state for existing features and collapse new features' coordinates
|
|
2782
2791
|
const ranges = this._findCollapsibleRanges();
|
|
2783
|
-
const featureRanges = ranges.filter(r => r.isRootFeature);
|
|
2784
2792
|
|
|
2785
2793
|
for (const range of ranges) {
|
|
2786
|
-
// Find which feature this range belongs to
|
|
2787
|
-
const featureIndex =
|
|
2788
|
-
|
|
2789
|
-
);
|
|
2794
|
+
// Find which feature this range belongs to using the correct featureRanges map
|
|
2795
|
+
const featureIndex = this._getFeatureIndexForLine(range.startLine);
|
|
2796
|
+
if (featureIndex === -1) continue;
|
|
2790
2797
|
|
|
2791
2798
|
if (featureIndex < existingFeatureCount) {
|
|
2792
2799
|
// Existing feature - restore collapsed state
|
|
@@ -3078,18 +3085,15 @@ class GeoJsonEditor extends HTMLElement {
|
|
|
3078
3085
|
private _applyCollapsedOption(collapsed: string[] | ((feature: Feature | null, index: number) => string[]), features: Feature[] | null = null): void {
|
|
3079
3086
|
const ranges = this._findCollapsibleRanges();
|
|
3080
3087
|
|
|
3081
|
-
// Group ranges by feature (root nodes)
|
|
3082
|
-
const featureRanges = ranges.filter(r => r.isRootFeature);
|
|
3083
|
-
|
|
3084
3088
|
// Determine which attributes to collapse per feature
|
|
3085
3089
|
for (const range of ranges) {
|
|
3086
3090
|
let shouldCollapse = false;
|
|
3087
3091
|
|
|
3088
3092
|
if (typeof collapsed === 'function') {
|
|
3089
|
-
// Find which feature this range belongs to
|
|
3090
|
-
const featureIndex =
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
+
// Find which feature this range belongs to using the correct featureRanges map
|
|
3094
|
+
const featureIndex = this._getFeatureIndexForLine(range.startLine);
|
|
3095
|
+
if (featureIndex === -1) continue;
|
|
3096
|
+
|
|
3093
3097
|
const feature = features?.[featureIndex] || null;
|
|
3094
3098
|
const collapsedAttrs = collapsed(feature, featureIndex);
|
|
3095
3099
|
|
|
@@ -3537,6 +3541,51 @@ class GeoJsonEditor extends HTMLElement {
|
|
|
3537
3541
|
}
|
|
3538
3542
|
}
|
|
3539
3543
|
|
|
3544
|
+
/**
|
|
3545
|
+
* Emit current-feature event when cursor moves to a different feature
|
|
3546
|
+
* Only emits when the feature changes (not on every cursor move)
|
|
3547
|
+
* @param force - If true, emit even if feature hasn't changed (used on focus)
|
|
3548
|
+
*/
|
|
3549
|
+
private _emitCurrentFeature(force: boolean = false): void {
|
|
3550
|
+
const featureIndex = this._getFeatureIndexForLine(this.cursorLine);
|
|
3551
|
+
|
|
3552
|
+
// Only emit if feature changed (unless forced)
|
|
3553
|
+
if (!force && featureIndex === this._lastCursorFeatureIndex) return;
|
|
3554
|
+
this._lastCursorFeatureIndex = featureIndex;
|
|
3555
|
+
|
|
3556
|
+
if (featureIndex === -1) {
|
|
3557
|
+
// Cursor is not in a feature
|
|
3558
|
+
this.dispatchEvent(new CustomEvent('current-feature', {
|
|
3559
|
+
detail: null,
|
|
3560
|
+
bubbles: true,
|
|
3561
|
+
composed: true
|
|
3562
|
+
}));
|
|
3563
|
+
} else {
|
|
3564
|
+
// Get the feature at cursor
|
|
3565
|
+
const features = this._parseFeatures();
|
|
3566
|
+
const feature = features[featureIndex] || null;
|
|
3567
|
+
|
|
3568
|
+
this.dispatchEvent(new CustomEvent('current-feature', {
|
|
3569
|
+
detail: feature,
|
|
3570
|
+
bubbles: true,
|
|
3571
|
+
composed: true
|
|
3572
|
+
}));
|
|
3573
|
+
}
|
|
3574
|
+
}
|
|
3575
|
+
|
|
3576
|
+
/**
|
|
3577
|
+
* Emit current-feature with null (used on blur)
|
|
3578
|
+
* Always emits to ensure map is cleared when editor loses focus
|
|
3579
|
+
*/
|
|
3580
|
+
private _emitCurrentFeatureNull(): void {
|
|
3581
|
+
this._lastCursorFeatureIndex = null;
|
|
3582
|
+
this.dispatchEvent(new CustomEvent('current-feature', {
|
|
3583
|
+
detail: null,
|
|
3584
|
+
bubbles: true,
|
|
3585
|
+
composed: true
|
|
3586
|
+
}));
|
|
3587
|
+
}
|
|
3588
|
+
|
|
3540
3589
|
// ========== UI Updates ==========
|
|
3541
3590
|
|
|
3542
3591
|
updateReadonly() {
|
|
@@ -3578,74 +3627,6 @@ class GeoJsonEditor extends HTMLElement {
|
|
|
3578
3627
|
if (this._editorSuffix) this._editorSuffix.textContent = this.suffix;
|
|
3579
3628
|
}
|
|
3580
3629
|
|
|
3581
|
-
// ========== Theme ==========
|
|
3582
|
-
|
|
3583
|
-
updateThemeCSS() {
|
|
3584
|
-
const darkSelector = this.getAttribute('dark-selector') || '.dark';
|
|
3585
|
-
const darkRule = parseSelectorToHostRule(darkSelector);
|
|
3586
|
-
|
|
3587
|
-
let themeStyle = this._id('theme-styles') as HTMLStyleElement;
|
|
3588
|
-
if (!themeStyle) {
|
|
3589
|
-
themeStyle = _ce('style') as HTMLStyleElement;
|
|
3590
|
-
themeStyle.id = 'theme-styles';
|
|
3591
|
-
this.shadowRoot!.insertBefore(themeStyle, this.shadowRoot!.firstChild);
|
|
3592
|
-
}
|
|
3593
|
-
|
|
3594
|
-
const darkDefaults = {
|
|
3595
|
-
bgColor: '#2b2b2b',
|
|
3596
|
-
textColor: '#a9b7c6',
|
|
3597
|
-
caretColor: '#bbb',
|
|
3598
|
-
gutterBg: '#313335',
|
|
3599
|
-
gutterBorder: '#3c3f41',
|
|
3600
|
-
gutterText: '#606366',
|
|
3601
|
-
jsonKey: '#9876aa',
|
|
3602
|
-
jsonString: '#6a8759',
|
|
3603
|
-
jsonNumber: '#6897bb',
|
|
3604
|
-
jsonBoolean: '#cc7832',
|
|
3605
|
-
jsonNull: '#cc7832',
|
|
3606
|
-
jsonPunct: '#a9b7c6',
|
|
3607
|
-
jsonError: '#ff6b68',
|
|
3608
|
-
controlColor: '#cc7832',
|
|
3609
|
-
controlBg: '#3c3f41',
|
|
3610
|
-
controlBorder: '#5a5a5a',
|
|
3611
|
-
geojsonKey: '#9876aa',
|
|
3612
|
-
geojsonType: '#6a8759',
|
|
3613
|
-
geojsonTypeInvalid: '#ff6b68',
|
|
3614
|
-
jsonKeyInvalid: '#ff6b68'
|
|
3615
|
-
};
|
|
3616
|
-
|
|
3617
|
-
RE_TO_KEBAB.lastIndex = 0;
|
|
3618
|
-
const toKebab = (str: string) => str.replace(RE_TO_KEBAB, '-$1').toLowerCase();
|
|
3619
|
-
const generateVars = (obj: Record<string, string | undefined>) => Object.entries(obj)
|
|
3620
|
-
.filter((entry): entry is [string, string] => entry[1] !== undefined)
|
|
3621
|
-
.map(([k, v]) => `--${toKebab(k)}: ${v};`)
|
|
3622
|
-
.join('\n ');
|
|
3623
|
-
|
|
3624
|
-
const lightVars = generateVars(this.themes.light as Record<string, string | undefined> || {});
|
|
3625
|
-
const darkTheme = { ...darkDefaults, ...this.themes.dark };
|
|
3626
|
-
const darkVars = generateVars(darkTheme as Record<string, string | undefined>);
|
|
3627
|
-
|
|
3628
|
-
let css = lightVars ? `:host {\n ${lightVars}\n }\n` : '';
|
|
3629
|
-
css += `${darkRule} {\n ${darkVars}\n }`;
|
|
3630
|
-
|
|
3631
|
-
themeStyle.textContent = css;
|
|
3632
|
-
}
|
|
3633
|
-
|
|
3634
|
-
setTheme(theme: ThemeSettings): void {
|
|
3635
|
-
if (theme.dark) this.themes.dark = { ...this.themes.dark, ...theme.dark };
|
|
3636
|
-
if (theme.light) this.themes.light = { ...this.themes.light, ...theme.light };
|
|
3637
|
-
this.updateThemeCSS();
|
|
3638
|
-
}
|
|
3639
|
-
|
|
3640
|
-
resetTheme(): void {
|
|
3641
|
-
this.themes = { dark: {}, light: {} };
|
|
3642
|
-
this.updateThemeCSS();
|
|
3643
|
-
}
|
|
3644
|
-
|
|
3645
|
-
getTheme(): ThemeSettings {
|
|
3646
|
-
return { ...this.themes };
|
|
3647
|
-
}
|
|
3648
|
-
|
|
3649
3630
|
/**
|
|
3650
3631
|
* Find all collapsible ranges using the mappings built by _rebuildNodeIdMappings
|
|
3651
3632
|
* This method only READS the existing mappings, it doesn't create new IDs
|
|
@@ -3863,19 +3844,17 @@ class GeoJsonEditor extends HTMLElement {
|
|
|
3863
3844
|
|
|
3864
3845
|
// Restore collapsed state for existing features
|
|
3865
3846
|
const ranges = this._findCollapsibleRanges();
|
|
3866
|
-
const featureRanges = ranges.filter(r => r.isRootFeature);
|
|
3867
3847
|
const actualNewCount = newCount !== undefined ? newCount : features.length - newStartIndex;
|
|
3868
3848
|
|
|
3869
3849
|
for (const range of ranges) {
|
|
3870
|
-
// Find which feature this range belongs to
|
|
3871
|
-
const featureIndex =
|
|
3872
|
-
|
|
3873
|
-
);
|
|
3850
|
+
// Find which feature this range belongs to using the correct featureRanges map
|
|
3851
|
+
const featureIndex = this._getFeatureIndexForLine(range.startLine);
|
|
3852
|
+
if (featureIndex === -1) continue;
|
|
3874
3853
|
|
|
3875
3854
|
// Determine if this is an existing feature (adjust index for insertAt case)
|
|
3876
3855
|
let originalFeatureIndex = featureIndex;
|
|
3877
3856
|
if (featureIndex >= newStartIndex && featureIndex < newStartIndex + actualNewCount) {
|
|
3878
|
-
// This is a new feature - apply options
|
|
3857
|
+
// This is a new feature - apply options later
|
|
3879
3858
|
continue;
|
|
3880
3859
|
} else if (featureIndex >= newStartIndex + actualNewCount) {
|
|
3881
3860
|
// Feature was shifted by insertion - adjust index
|
|
@@ -3911,13 +3890,11 @@ class GeoJsonEditor extends HTMLElement {
|
|
|
3911
3890
|
if (!collapsed || (Array.isArray(collapsed) && collapsed.length === 0)) return;
|
|
3912
3891
|
|
|
3913
3892
|
const ranges = this._findCollapsibleRanges();
|
|
3914
|
-
const featureRanges = ranges.filter(r => r.isRootFeature);
|
|
3915
3893
|
|
|
3916
3894
|
for (const range of ranges) {
|
|
3917
|
-
// Find which feature this range belongs to
|
|
3918
|
-
const featureIndex =
|
|
3919
|
-
|
|
3920
|
-
);
|
|
3895
|
+
// Find which feature this range belongs to using the correct featureRanges map
|
|
3896
|
+
const featureIndex = this._getFeatureIndexForLine(range.startLine);
|
|
3897
|
+
if (featureIndex === -1) continue;
|
|
3921
3898
|
|
|
3922
3899
|
// Only process new features
|
|
3923
3900
|
if (featureIndex < startIndex || featureIndex >= startIndex + count) continue;
|
|
@@ -4046,12 +4023,12 @@ class GeoJsonEditor extends HTMLElement {
|
|
|
4046
4023
|
|
|
4047
4024
|
// Restore collapsed state for remaining features
|
|
4048
4025
|
const ranges = this._findCollapsibleRanges();
|
|
4049
|
-
const featureRanges = ranges.filter(r => r.isRootFeature);
|
|
4050
4026
|
|
|
4051
4027
|
for (const range of ranges) {
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
);
|
|
4028
|
+
// Find which feature this range belongs to using the correct featureRanges map
|
|
4029
|
+
const featureIndex = this._getFeatureIndexForLine(range.startLine);
|
|
4030
|
+
if (featureIndex === -1) continue;
|
|
4031
|
+
|
|
4055
4032
|
const key = `${featureIndex}:${range.nodeKey}`;
|
|
4056
4033
|
if (collapsedKeys.has(key)) {
|
|
4057
4034
|
this.collapsedNodes.add(range.nodeId);
|
|
@@ -4075,6 +4052,10 @@ class GeoJsonEditor extends HTMLElement {
|
|
|
4075
4052
|
this.lines = [];
|
|
4076
4053
|
this.collapsedNodes.clear();
|
|
4077
4054
|
this.hiddenFeatures.clear();
|
|
4055
|
+
this.cursorLine = 0;
|
|
4056
|
+
this.cursorColumn = 0;
|
|
4057
|
+
this.selectionStart = null;
|
|
4058
|
+
this.selectionEnd = null;
|
|
4078
4059
|
this.updateModel();
|
|
4079
4060
|
this.scheduleRender();
|
|
4080
4061
|
this.updatePlaceholderVisibility();
|
package/src/types.ts
CHANGED
|
@@ -16,33 +16,3 @@ export interface SetOptions {
|
|
|
16
16
|
*/
|
|
17
17
|
collapsed?: string[] | ((feature: Feature | null, index: number) => string[]);
|
|
18
18
|
}
|
|
19
|
-
|
|
20
|
-
/** Theme configuration */
|
|
21
|
-
export interface ThemeConfig {
|
|
22
|
-
bgColor?: string;
|
|
23
|
-
textColor?: string;
|
|
24
|
-
caretColor?: string;
|
|
25
|
-
gutterBg?: string;
|
|
26
|
-
gutterBorder?: string;
|
|
27
|
-
gutterText?: string;
|
|
28
|
-
jsonKey?: string;
|
|
29
|
-
jsonString?: string;
|
|
30
|
-
jsonNumber?: string;
|
|
31
|
-
jsonBoolean?: string;
|
|
32
|
-
jsonNull?: string;
|
|
33
|
-
jsonPunct?: string;
|
|
34
|
-
jsonError?: string;
|
|
35
|
-
controlColor?: string;
|
|
36
|
-
controlBg?: string;
|
|
37
|
-
controlBorder?: string;
|
|
38
|
-
geojsonKey?: string;
|
|
39
|
-
geojsonType?: string;
|
|
40
|
-
geojsonTypeInvalid?: string;
|
|
41
|
-
jsonKeyInvalid?: string;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/** Theme settings for dark and light modes */
|
|
45
|
-
export interface ThemeSettings {
|
|
46
|
-
dark?: ThemeConfig;
|
|
47
|
-
light?: ThemeConfig;
|
|
48
|
-
}
|