@deck.gl-community/editable-layers 9.2.0-beta.8 → 9.2.5

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.
Files changed (96) hide show
  1. package/dist/edit-modes/draw-90degree-polygon-mode.d.ts.map +1 -1
  2. package/dist/edit-modes/draw-90degree-polygon-mode.js.map +1 -1
  3. package/dist/edit-modes/draw-line-string-mode.d.ts.map +1 -1
  4. package/dist/edit-modes/draw-line-string-mode.js.map +1 -1
  5. package/dist/edit-modes/draw-polygon-mode.d.ts.map +1 -1
  6. package/dist/edit-modes/draw-polygon-mode.js +48 -52
  7. package/dist/edit-modes/draw-polygon-mode.js.map +1 -1
  8. package/dist/edit-modes/draw-square-mode.d.ts.map +1 -1
  9. package/dist/edit-modes/draw-square-mode.js.map +1 -1
  10. package/dist/edit-modes/extend-line-string-mode.d.ts.map +1 -1
  11. package/dist/edit-modes/extend-line-string-mode.js.map +1 -1
  12. package/dist/edit-modes/geojson-edit-mode.d.ts.map +1 -1
  13. package/dist/edit-modes/geojson-edit-mode.js.map +1 -1
  14. package/dist/edit-modes/modify-mode.d.ts.map +1 -1
  15. package/dist/edit-modes/modify-mode.js +23 -19
  16. package/dist/edit-modes/modify-mode.js.map +1 -1
  17. package/dist/edit-modes/resize-circle-mode.d.ts.map +1 -1
  18. package/dist/edit-modes/resize-circle-mode.js.map +1 -1
  19. package/dist/edit-modes/rotate-mode.d.ts.map +1 -1
  20. package/dist/edit-modes/rotate-mode.js.map +1 -1
  21. package/dist/edit-modes/scale-mode.d.ts.map +1 -1
  22. package/dist/edit-modes/scale-mode.js.map +1 -1
  23. package/dist/edit-modes/snappable-mode.d.ts.map +1 -1
  24. package/dist/edit-modes/snappable-mode.js.map +1 -1
  25. package/dist/edit-modes/split-polygon-mode.d.ts.map +1 -1
  26. package/dist/edit-modes/split-polygon-mode.js.map +1 -1
  27. package/dist/edit-modes/three-click-polygon-mode.d.ts.map +1 -1
  28. package/dist/edit-modes/three-click-polygon-mode.js +14 -18
  29. package/dist/edit-modes/three-click-polygon-mode.js.map +1 -1
  30. package/dist/edit-modes/translate-mode.d.ts.map +1 -1
  31. package/dist/edit-modes/translate-mode.js +2 -2
  32. package/dist/edit-modes/translate-mode.js.map +1 -1
  33. package/dist/edit-modes/two-click-polygon-mode.d.ts.map +1 -1
  34. package/dist/edit-modes/two-click-polygon-mode.js.map +1 -1
  35. package/dist/edit-modes/utils.js +1 -1
  36. package/dist/edit-modes/utils.js.map +1 -1
  37. package/dist/editable-layers/editable-geojson-layer.d.ts.map +1 -1
  38. package/dist/editable-layers/editable-geojson-layer.js +20 -4
  39. package/dist/editable-layers/editable-geojson-layer.js.map +1 -1
  40. package/dist/index.cjs +246 -55
  41. package/dist/index.cjs.map +4 -4
  42. package/dist/index.d.ts +2 -0
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +1 -0
  45. package/dist/index.js.map +1 -1
  46. package/dist/mode-handlers/mode-handler.d.ts.map +1 -1
  47. package/dist/mode-handlers/mode-handler.js +1 -1
  48. package/dist/mode-handlers/mode-handler.js.map +1 -1
  49. package/dist/mode-handlers/rotate-handler.d.ts.map +1 -1
  50. package/dist/mode-handlers/rotate-handler.js.map +1 -1
  51. package/dist/mode-handlers/scale-handler.d.ts.map +1 -1
  52. package/dist/mode-handlers/scale-handler.js.map +1 -1
  53. package/dist/mode-handlers/split-polygon-handler.d.ts.map +1 -1
  54. package/dist/mode-handlers/split-polygon-handler.js.map +1 -1
  55. package/dist/utils/geojson-types.d.ts.map +1 -1
  56. package/dist/utils/translate-from-center.d.ts.map +1 -1
  57. package/dist/utils/translate-from-center.js.map +1 -1
  58. package/dist/utils/utils.js +1 -1
  59. package/dist/utils/utils.js.map +1 -1
  60. package/dist/widgets/edit-mode-tray-widget.d.ts +1 -0
  61. package/dist/widgets/edit-mode-tray-widget.d.ts.map +1 -1
  62. package/dist/widgets/edit-mode-tray-widget.js +1 -0
  63. package/dist/widgets/edit-mode-tray-widget.js.map +1 -1
  64. package/dist/widgets/editor-toolbar-widget.d.ts +40 -0
  65. package/dist/widgets/editor-toolbar-widget.d.ts.map +1 -0
  66. package/dist/widgets/editor-toolbar-widget.js +170 -0
  67. package/dist/widgets/editor-toolbar-widget.js.map +1 -0
  68. package/package.json +16 -12
  69. package/src/edit-modes/draw-90degree-polygon-mode.ts +7 -1
  70. package/src/edit-modes/draw-line-string-mode.ts +6 -1
  71. package/src/edit-modes/draw-polygon-mode.ts +96 -117
  72. package/src/edit-modes/draw-square-mode.ts +0 -1
  73. package/src/edit-modes/extend-line-string-mode.ts +9 -2
  74. package/src/edit-modes/geojson-edit-mode.ts +15 -3
  75. package/src/edit-modes/immutable-feature-collection.ts +1 -1
  76. package/src/edit-modes/modify-mode.ts +33 -21
  77. package/src/edit-modes/resize-circle-mode.ts +8 -3
  78. package/src/edit-modes/rotate-mode.ts +3 -8
  79. package/src/edit-modes/scale-mode.ts +5 -6
  80. package/src/edit-modes/snappable-mode.ts +6 -1
  81. package/src/edit-modes/split-polygon-mode.ts +9 -3
  82. package/src/edit-modes/three-click-polygon-mode.ts +28 -31
  83. package/src/edit-modes/translate-mode.ts +8 -3
  84. package/src/edit-modes/two-click-polygon-mode.ts +7 -1
  85. package/src/edit-modes/utils.ts +1 -1
  86. package/src/editable-layers/editable-geojson-layer.ts +20 -4
  87. package/src/index.ts +3 -0
  88. package/src/mode-handlers/mode-handler.ts +21 -6
  89. package/src/mode-handlers/rotate-handler.ts +1 -4
  90. package/src/mode-handlers/scale-handler.ts +3 -7
  91. package/src/mode-handlers/split-polygon-handler.ts +3 -1
  92. package/src/utils/geojson-types.ts +22 -6
  93. package/src/utils/translate-from-center.ts +11 -24
  94. package/src/utils/utils.ts +1 -1
  95. package/src/widgets/edit-mode-tray-widget.tsx +2 -6
  96. package/src/widgets/editor-toolbar-widget.tsx +348 -0
@@ -0,0 +1,348 @@
1
+ // deck.gl-community
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import {render} from 'preact';
6
+ import type {JSX} from 'preact';
7
+ import {Widget, type WidgetProps, type WidgetPlacement, type Deck} from '@deck.gl/core';
8
+
9
+ export type BooleanOperation = 'union' | 'difference' | 'intersection' | null;
10
+
11
+ export type EditorToolbarWidgetProps = WidgetProps & {
12
+ /** Placement for the widget root element. */
13
+ placement?: WidgetPlacement;
14
+ /** Currently active boolean operation. */
15
+ booleanOperation?: BooleanOperation;
16
+ /** Number of features in the current dataset. */
17
+ featureCount?: number;
18
+ /** Callback fired when the user selects a boolean operation. */
19
+ onSetBooleanOperation?: (op: BooleanOperation) => void;
20
+ /** Callback fired when the user clicks the clear button. */
21
+ onClear?: () => void;
22
+ /** Callback fired when the user clicks the export button. */
23
+ onExport?: () => void;
24
+ };
25
+
26
+ // --- Styles (match EditModeTrayWidget visual language) ---
27
+
28
+ const ROOT_STYLE: Partial<CSSStyleDeclaration> = {
29
+ position: 'absolute',
30
+ display: 'flex',
31
+ flexDirection: 'column',
32
+ gap: '6px',
33
+ pointerEvents: 'auto',
34
+ userSelect: 'none',
35
+ zIndex: '99'
36
+ };
37
+
38
+ const TRAY_STYLE: JSX.CSSProperties = {
39
+ display: 'flex',
40
+ gap: '4px',
41
+ background: 'rgba(36, 40, 41, 0.88)',
42
+ borderRadius: '999px',
43
+ padding: '5px 8px',
44
+ alignItems: 'center',
45
+ justifyContent: 'center',
46
+ boxShadow: '0 4px 12px rgba(0, 0, 0, 0.25)'
47
+ };
48
+
49
+ const BUTTON_STYLE: JSX.CSSProperties = {
50
+ appearance: 'none',
51
+ background: 'transparent',
52
+ border: 'none',
53
+ color: '#f0f0f0',
54
+ height: '30px',
55
+ padding: '0 8px',
56
+ display: 'flex',
57
+ alignItems: 'center',
58
+ justifyContent: 'center',
59
+ gap: '4px',
60
+ borderRadius: '15px',
61
+ cursor: 'pointer',
62
+ fontSize: '11px',
63
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
64
+ fontWeight: '500',
65
+ transition: 'background 0.15s ease, color 0.15s ease, box-shadow 0.15s ease',
66
+ whiteSpace: 'nowrap'
67
+ };
68
+
69
+ const BUTTON_ACTIVE_STYLE: JSX.CSSProperties = {
70
+ background: '#0071e3',
71
+ color: '#ffffff',
72
+ boxShadow: '0 0 0 2px rgba(255, 255, 255, 0.35)'
73
+ };
74
+
75
+ const DIVIDER_STYLE: JSX.CSSProperties = {
76
+ width: '1px',
77
+ height: '20px',
78
+ background: 'rgba(255, 255, 255, 0.2)',
79
+ margin: '0 2px',
80
+ flexShrink: '0'
81
+ };
82
+
83
+ const BADGE_STYLE: JSX.CSSProperties = {
84
+ color: 'rgba(255, 255, 255, 0.6)',
85
+ fontSize: '11px',
86
+ padding: '0 6px',
87
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
88
+ whiteSpace: 'nowrap'
89
+ };
90
+
91
+ // --- SVG Icons (14x14) ---
92
+
93
+ function PencilIcon() {
94
+ return (
95
+ <svg
96
+ width="14"
97
+ height="14"
98
+ viewBox="0 0 14 14"
99
+ fill="none"
100
+ stroke="currentColor"
101
+ strokeWidth="1.5"
102
+ strokeLinecap="round"
103
+ strokeLinejoin="round"
104
+ >
105
+ <path d="M10 1.5l2.5 2.5L4.5 12H2v-2.5z" />
106
+ </svg>
107
+ );
108
+ }
109
+
110
+ function SubtractIcon() {
111
+ return (
112
+ <svg
113
+ width="14"
114
+ height="14"
115
+ viewBox="0 0 14 14"
116
+ fill="none"
117
+ stroke="currentColor"
118
+ strokeWidth="1.1"
119
+ >
120
+ <rect x="1" y="1" width="8" height="8" rx="1.5" />
121
+ <rect x="5" y="5" width="8" height="8" rx="1.5" fill="rgba(36,40,41,0.88)" />
122
+ </svg>
123
+ );
124
+ }
125
+
126
+ function UnionIcon() {
127
+ return (
128
+ <svg
129
+ width="14"
130
+ height="14"
131
+ viewBox="0 0 14 14"
132
+ fill="none"
133
+ stroke="currentColor"
134
+ strokeWidth="1.1"
135
+ >
136
+ <rect x="1" y="1" width="8" height="8" rx="1.5" />
137
+ <rect x="5" y="5" width="8" height="8" rx="1.5" />
138
+ </svg>
139
+ );
140
+ }
141
+
142
+ function IntersectIcon() {
143
+ return (
144
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none" strokeWidth="1.1">
145
+ <rect x="1" y="1" width="8" height="8" rx="1.5" stroke="currentColor" opacity="0.4" />
146
+ <rect x="5" y="5" width="8" height="8" rx="1.5" stroke="currentColor" opacity="0.4" />
147
+ <rect x="5" y="5" width="4" height="4" fill="currentColor" opacity="0.6" />
148
+ </svg>
149
+ );
150
+ }
151
+
152
+ function TrashIcon() {
153
+ return (
154
+ <svg
155
+ width="14"
156
+ height="14"
157
+ viewBox="0 0 14 14"
158
+ fill="none"
159
+ stroke="currentColor"
160
+ strokeWidth="1.3"
161
+ strokeLinecap="round"
162
+ strokeLinejoin="round"
163
+ >
164
+ <path d="M2.5 4h9M5.5 4V2.5h3V4M3.5 4l.5 8h6l.5-8" />
165
+ </svg>
166
+ );
167
+ }
168
+
169
+ function DownloadIcon() {
170
+ return (
171
+ <svg
172
+ width="14"
173
+ height="14"
174
+ viewBox="0 0 14 14"
175
+ fill="none"
176
+ stroke="currentColor"
177
+ strokeWidth="1.3"
178
+ strokeLinecap="round"
179
+ strokeLinejoin="round"
180
+ >
181
+ <path d="M7 2v7M4.5 6.5L7 9l2.5-2.5M2.5 11.5h9" />
182
+ </svg>
183
+ );
184
+ }
185
+
186
+ // --- Widget ---
187
+
188
+ const BOOLEAN_OPS: Array<{
189
+ op: BooleanOperation;
190
+ icon: () => JSX.Element;
191
+ label: string;
192
+ title: string;
193
+ }> = [
194
+ {op: null, icon: PencilIcon, label: 'Edit', title: 'Draw new features'},
195
+ {op: 'difference', icon: SubtractIcon, label: 'Sub', title: 'Subtract from selection'},
196
+ {op: 'union', icon: UnionIcon, label: 'Union', title: 'Union with selection'},
197
+ {op: 'intersection', icon: IntersectIcon, label: 'Sect', title: 'Intersect with selection'}
198
+ ];
199
+
200
+ export class EditorToolbarWidget extends Widget<EditorToolbarWidgetProps> {
201
+ static override defaultProps = {
202
+ id: 'editor-toolbar',
203
+ _container: null,
204
+ placement: 'bottom-left',
205
+ booleanOperation: null,
206
+ featureCount: 0,
207
+ style: {},
208
+ className: ''
209
+ } satisfies Required<WidgetProps> &
210
+ Required<Pick<EditorToolbarWidgetProps, 'placement'>> &
211
+ EditorToolbarWidgetProps;
212
+
213
+ placement: WidgetPlacement = 'bottom-left';
214
+ className = 'deck-widget-editor-toolbar';
215
+ deck?: Deck | null = null;
216
+ private appliedCustomClassName: string | null = null;
217
+
218
+ constructor(props: EditorToolbarWidgetProps = {}) {
219
+ super({...EditorToolbarWidget.defaultProps, ...props});
220
+ this.placement = props.placement ?? EditorToolbarWidget.defaultProps.placement;
221
+ }
222
+
223
+ override setProps(props: Partial<EditorToolbarWidgetProps>): void {
224
+ if (props.placement !== undefined) {
225
+ this.placement = props.placement;
226
+ }
227
+ super.setProps(props);
228
+ this.renderToolbar();
229
+ }
230
+
231
+ override onAdd({deck}: {deck: Deck}): void {
232
+ this.deck = deck;
233
+ }
234
+
235
+ override onRemove(): void {
236
+ this.deck = null;
237
+ const root = this.rootElement;
238
+ if (root) {
239
+ render(null, root);
240
+ }
241
+ this.rootElement = null;
242
+ }
243
+
244
+ override onRenderHTML(rootElement: HTMLElement): void {
245
+ const style = {...ROOT_STYLE, ...this.props.style};
246
+ Object.assign(rootElement.style, style);
247
+ if (this.appliedCustomClassName && this.appliedCustomClassName !== this.props.className) {
248
+ rootElement.classList.remove(this.appliedCustomClassName);
249
+ this.appliedCustomClassName = null;
250
+ }
251
+ if (this.props.className) {
252
+ rootElement.classList.add(this.props.className);
253
+ this.appliedCustomClassName = this.props.className;
254
+ }
255
+ rootElement.classList.add(this.className);
256
+ this.renderToolbar();
257
+ }
258
+
259
+ private renderToolbar() {
260
+ const root = this.rootElement;
261
+ if (!root) {
262
+ return;
263
+ }
264
+
265
+ const booleanOp = this.props.booleanOperation ?? null;
266
+ const featureCount = this.props.featureCount ?? 0;
267
+
268
+ const stopEvent = (event: Event) => {
269
+ event.stopPropagation();
270
+ if (typeof (event as any).stopImmediatePropagation === 'function') {
271
+ (event as any).stopImmediatePropagation();
272
+ }
273
+ };
274
+
275
+ const ui = (
276
+ <div
277
+ style={TRAY_STYLE}
278
+ onPointerDown={stopEvent}
279
+ onPointerMove={stopEvent}
280
+ onPointerUp={stopEvent}
281
+ onMouseDown={stopEvent}
282
+ onMouseMove={stopEvent}
283
+ onMouseUp={stopEvent}
284
+ onTouchStart={stopEvent}
285
+ onTouchMove={stopEvent}
286
+ onTouchEnd={stopEvent}
287
+ >
288
+ {/* Boolean operation toggle buttons */}
289
+ {BOOLEAN_OPS.map(({op, icon: Icon, label, title}) => {
290
+ const active = booleanOp === op;
291
+ return (
292
+ <button
293
+ key={label}
294
+ type="button"
295
+ title={title}
296
+ aria-pressed={active}
297
+ style={{...BUTTON_STYLE, ...(active ? BUTTON_ACTIVE_STYLE : {})}}
298
+ onClick={(event) => {
299
+ stopEvent(event);
300
+ this.props.onSetBooleanOperation?.(op);
301
+ }}
302
+ >
303
+ <Icon />
304
+ <span>{label}</span>
305
+ </button>
306
+ );
307
+ })}
308
+
309
+ <div style={DIVIDER_STYLE} />
310
+
311
+ {/* Clear button */}
312
+ <button
313
+ type="button"
314
+ title="Clear all features"
315
+ style={BUTTON_STYLE}
316
+ onClick={(event) => {
317
+ stopEvent(event);
318
+ this.props.onClear?.();
319
+ }}
320
+ >
321
+ <TrashIcon />
322
+ </button>
323
+
324
+ {/* Export button */}
325
+ <button
326
+ type="button"
327
+ title="Download as GeoJSON"
328
+ style={BUTTON_STYLE}
329
+ onClick={(event) => {
330
+ stopEvent(event);
331
+ this.props.onExport?.();
332
+ }}
333
+ >
334
+ <DownloadIcon />
335
+ </button>
336
+
337
+ <div style={DIVIDER_STYLE} />
338
+
339
+ {/* Feature count badge */}
340
+ <span style={BADGE_STYLE}>
341
+ {featureCount} feature{featureCount !== 1 ? 's' : ''}
342
+ </span>
343
+ </div>
344
+ );
345
+
346
+ render(ui, root);
347
+ }
348
+ }